Making more of the C++ layer buildable

This commit is contained in:
Eric Froemling 2020-10-12 11:58:49 -07:00
parent 8198788228
commit ae1f93b87f
164 changed files with 38709 additions and 652 deletions

View File

@ -3932,24 +3932,24 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/88/6c/03d5c4811e2ffb2f341a042f676e",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3f/c0/7025f06247748ad8f0516389e4f8",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c9/36/4393ce5c02ebf21a6a2dbb0614eb",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/13/7d/2c551af0ebc26c93f52a87b02ca0",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/eb/b2/ff5199a4437852f09d2d7096896d",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/06/5a/d683bd2197262a9baec17b7306a6",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/26/07/08fe94d637f560acfd450d722393",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cc/ae/2855e9d714ea0c7ceaf4f42a4dc2",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/46/fc/16cf6c1cb45381b377c1d3bac058",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/81/de/d2b16c91eed65ab721149488f399",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/5d/89/1ec1c4058821e52e117142c7fbc4",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/9e/90/315e8edc3ab7bc1080d18e29cf8d",
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ea/68/8d61d116af2df5617a11e5ae2d9d",
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b9/62/fa796628f2840d880dd421f9c821",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f0/d0/e1e69b545cf166ce4e679621307f",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b1/d0/14df0a36c445e8a2e67eb8802911",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/26/eb/3f9b13ea38c9f7af8ff0532258e8",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cc/56/442362d6eab98a42da31ba8cc9d6",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/f9/dd209379992a04c479f4cb5e3e85",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b0/d7/5ec4bfbf10cf520d2c2b85530176"
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/7d/82/ee9dbd4e5f7979376228d6953bf2",
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8b/1b/28311c0a6459e5d4fba5b8646f3e",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/01/bb/1aa148fa278d31cb6674174b194f",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/98/7b/5f1bcd5a6ac1916ca35e10c28a30",
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/64/03/694933268214ba930b6f38b9c6c0",
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e1/49/88c92b0f65857a88f6eaaee04483",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a0/f6/39e7caccbbcd4cd442145c17a05f",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1d/c6/20d9325e37de411bc6fe3d41d503",
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ab/14/4cf9664fb0de75dcb20b596ded74",
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c3/9a/39b182b32184ae177611078f03ff",
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/70/e2/b993cf8dd5ede4d9e6ce42cd8c24",
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/60/03/166e19fae3db8f6e4b5c6a59a520",
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e8/ed/62a752dbeeca8f5e99725d42e7e7",
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4c/a9/568023651355fdd0ce7a865c2872",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0d/25/2d023ef6acd6ae5aa09bf12158d3",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/42/be/79eec8bc7b2cc914cc6cb8ed0769",
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/83/00/c2e4c96a434c1ac16e9f8b8a401e",
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/03/94/b12016e90c0c650b3c3222bc2453",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f2/9e/fd1ff58204bf80d0b02edfa79293",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/26/5c/a738eaa3e553bb00814f2376cb5a"
}

View File

@ -1009,6 +1009,7 @@
<w>installdir</w>
<w>instancer</w>
<w>interfacetype</w>
<w>internalsrc</w>
<w>interstitials</w>
<w>intex</w>
<w>intp</w>
@ -1370,8 +1371,10 @@
<w>nospeak</w>
<w>nosub</w>
<w>nosyncdir</w>
<w>nosyncdirlist</w>
<w>nosyncdirs</w>
<w>nosyncfile</w>
<w>nosyncfilelist</w>
<w>nosyncfiles</w>
<w>nosynctool</w>
<w>nosynctools</w>
@ -1821,6 +1824,7 @@
<w>setactivity</w>
<w>setalpha</w>
<w>setbuild</w>
<w>setconfig</w>
<w>setdata</w>
<w>setlanguage</w>
<w>setmusic</w>

View File

@ -174,6 +174,441 @@ add_executable(ballisticacore
${BA_SRC_ROOT}/external/qr_code_generator/QrCode.cpp
# AUTOGENERATED_PUBLIC_BEGIN (this section is managed by the "update_project" tool)
${BA_SRC_ROOT}/ballistica/app/app.cc
${BA_SRC_ROOT}/ballistica/app/app.h
${BA_SRC_ROOT}/ballistica/app/app_config.h
${BA_SRC_ROOT}/ballistica/app/app_globals.h
${BA_SRC_ROOT}/ballistica/app/headless_app.h
${BA_SRC_ROOT}/ballistica/app/stress_test.h
${BA_SRC_ROOT}/ballistica/app/vr_app.h
${BA_SRC_ROOT}/ballistica/audio/al_sys.cc
${BA_SRC_ROOT}/ballistica/audio/al_sys.h
${BA_SRC_ROOT}/ballistica/audio/audio.cc
${BA_SRC_ROOT}/ballistica/audio/audio.h
${BA_SRC_ROOT}/ballistica/audio/audio_server.cc
${BA_SRC_ROOT}/ballistica/audio/audio_server.h
${BA_SRC_ROOT}/ballistica/audio/audio_source.cc
${BA_SRC_ROOT}/ballistica/audio/audio_source.h
${BA_SRC_ROOT}/ballistica/audio/audio_streamer.cc
${BA_SRC_ROOT}/ballistica/audio/audio_streamer.h
${BA_SRC_ROOT}/ballistica/audio/ogg_stream.cc
${BA_SRC_ROOT}/ballistica/audio/ogg_stream.h
${BA_SRC_ROOT}/ballistica/ballistica.cc
${BA_SRC_ROOT}/ballistica/ballistica.h
${BA_SRC_ROOT}/ballistica/config/config_cmake.h
${BA_SRC_ROOT}/ballistica/config/config_common.h
${BA_SRC_ROOT}/ballistica/core/context.cc
${BA_SRC_ROOT}/ballistica/core/context.h
${BA_SRC_ROOT}/ballistica/core/exception.cc
${BA_SRC_ROOT}/ballistica/core/exception.h
${BA_SRC_ROOT}/ballistica/core/fatal_error.cc
${BA_SRC_ROOT}/ballistica/core/fatal_error.h
${BA_SRC_ROOT}/ballistica/core/inline.cc
${BA_SRC_ROOT}/ballistica/core/inline.h
${BA_SRC_ROOT}/ballistica/core/logging.cc
${BA_SRC_ROOT}/ballistica/core/logging.h
${BA_SRC_ROOT}/ballistica/core/macros.cc
${BA_SRC_ROOT}/ballistica/core/macros.h
${BA_SRC_ROOT}/ballistica/core/module.cc
${BA_SRC_ROOT}/ballistica/core/module.h
${BA_SRC_ROOT}/ballistica/core/object.cc
${BA_SRC_ROOT}/ballistica/core/object.h
${BA_SRC_ROOT}/ballistica/core/thread.cc
${BA_SRC_ROOT}/ballistica/core/thread.h
${BA_SRC_ROOT}/ballistica/core/types.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics.cc
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_fuse.cc
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_fuse.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_fuse_data.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_height_cache.cc
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_height_cache.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_server.cc
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_server.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_shadow.cc
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_shadow.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_shadow_data.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_volume_light.cc
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_volume_light.h
${BA_SRC_ROOT}/ballistica/dynamics/bg/bg_dynamics_volume_light_data.h
${BA_SRC_ROOT}/ballistica/dynamics/collision.h
${BA_SRC_ROOT}/ballistica/dynamics/collision_cache.cc
${BA_SRC_ROOT}/ballistica/dynamics/collision_cache.h
${BA_SRC_ROOT}/ballistica/dynamics/dynamics.cc
${BA_SRC_ROOT}/ballistica/dynamics/dynamics.h
${BA_SRC_ROOT}/ballistica/dynamics/material/impact_sound_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/impact_sound_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/material.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/material.h
${BA_SRC_ROOT}/ballistica/dynamics/material/material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/material_component.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/material_component.h
${BA_SRC_ROOT}/ballistica/dynamics/material/material_condition_node.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/material_condition_node.h
${BA_SRC_ROOT}/ballistica/dynamics/material/material_context.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/material_context.h
${BA_SRC_ROOT}/ballistica/dynamics/material/node_message_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/node_message_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/node_mod_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/node_mod_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/node_user_message_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/node_user_message_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/part_mod_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/part_mod_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/python_call_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/python_call_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/roll_sound_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/roll_sound_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/skid_sound_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/skid_sound_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/material/sound_material_action.cc
${BA_SRC_ROOT}/ballistica/dynamics/material/sound_material_action.h
${BA_SRC_ROOT}/ballistica/dynamics/part.cc
${BA_SRC_ROOT}/ballistica/dynamics/part.h
${BA_SRC_ROOT}/ballistica/dynamics/rigid_body.cc
${BA_SRC_ROOT}/ballistica/dynamics/rigid_body.h
${BA_SRC_ROOT}/ballistica/game/account.h
${BA_SRC_ROOT}/ballistica/game/client_controller_interface.h
${BA_SRC_ROOT}/ballistica/game/connection/connection.h
${BA_SRC_ROOT}/ballistica/game/connection/connection_to_client.h
${BA_SRC_ROOT}/ballistica/game/connection/connection_to_client_udp.h
${BA_SRC_ROOT}/ballistica/game/connection/connection_to_host.h
${BA_SRC_ROOT}/ballistica/game/connection/connection_to_host_udp.h
${BA_SRC_ROOT}/ballistica/game/friend_score_set.h
${BA_SRC_ROOT}/ballistica/game/game.h
${BA_SRC_ROOT}/ballistica/game/game_stream.h
${BA_SRC_ROOT}/ballistica/game/host_activity.h
${BA_SRC_ROOT}/ballistica/game/player.h
${BA_SRC_ROOT}/ballistica/game/player_spec.h
${BA_SRC_ROOT}/ballistica/game/score_to_beat.h
${BA_SRC_ROOT}/ballistica/game/session/client_session.h
${BA_SRC_ROOT}/ballistica/game/session/host_session.h
${BA_SRC_ROOT}/ballistica/game/session/net_client_session.h
${BA_SRC_ROOT}/ballistica/game/session/replay_client_session.h
${BA_SRC_ROOT}/ballistica/game/session/session.h
${BA_SRC_ROOT}/ballistica/generic/base64.cc
${BA_SRC_ROOT}/ballistica/generic/base64.h
${BA_SRC_ROOT}/ballistica/generic/buffer.h
${BA_SRC_ROOT}/ballistica/generic/huffman.cc
${BA_SRC_ROOT}/ballistica/generic/huffman.h
${BA_SRC_ROOT}/ballistica/generic/json.cc
${BA_SRC_ROOT}/ballistica/generic/json.h
${BA_SRC_ROOT}/ballistica/generic/lambda_runnable.h
${BA_SRC_ROOT}/ballistica/generic/real_timer.h
${BA_SRC_ROOT}/ballistica/generic/runnable.cc
${BA_SRC_ROOT}/ballistica/generic/runnable.h
${BA_SRC_ROOT}/ballistica/generic/timer.cc
${BA_SRC_ROOT}/ballistica/generic/timer.h
${BA_SRC_ROOT}/ballistica/generic/timer_list.cc
${BA_SRC_ROOT}/ballistica/generic/timer_list.h
${BA_SRC_ROOT}/ballistica/generic/utf8.cc
${BA_SRC_ROOT}/ballistica/generic/utf8.h
${BA_SRC_ROOT}/ballistica/generic/utils.cc
${BA_SRC_ROOT}/ballistica/generic/utils.h
${BA_SRC_ROOT}/ballistica/graphics/area_of_interest.cc
${BA_SRC_ROOT}/ballistica/graphics/area_of_interest.h
${BA_SRC_ROOT}/ballistica/graphics/camera.cc
${BA_SRC_ROOT}/ballistica/graphics/camera.h
${BA_SRC_ROOT}/ballistica/graphics/component/empty_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/object_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/object_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/post_process_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/post_process_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/render_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/render_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/shield_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/shield_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/simple_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/simple_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/smoke_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/smoke_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/special_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/special_component.h
${BA_SRC_ROOT}/ballistica/graphics/component/sprite_component.cc
${BA_SRC_ROOT}/ballistica/graphics/component/sprite_component.h
${BA_SRC_ROOT}/ballistica/graphics/frame_def.cc
${BA_SRC_ROOT}/ballistica/graphics/frame_def.h
${BA_SRC_ROOT}/ballistica/graphics/framebuffer.h
${BA_SRC_ROOT}/ballistica/graphics/gl/gl_sys.cc
${BA_SRC_ROOT}/ballistica/graphics/gl/gl_sys.h
${BA_SRC_ROOT}/ballistica/graphics/gl/renderer_gl.cc
${BA_SRC_ROOT}/ballistica/graphics/gl/renderer_gl.h
${BA_SRC_ROOT}/ballistica/graphics/graphics.cc
${BA_SRC_ROOT}/ballistica/graphics/graphics.h
${BA_SRC_ROOT}/ballistica/graphics/graphics_server.cc
${BA_SRC_ROOT}/ballistica/graphics/graphics_server.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/image_mesh.cc
${BA_SRC_ROOT}/ballistica/graphics/mesh/image_mesh.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_buffer.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_buffer_base.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_buffer_vertex_simple_full.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_buffer_vertex_smoke_full.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_buffer_vertex_sprite.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_data.cc
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_data.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_data_client_handle.cc
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_data_client_handle.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_index_buffer_16.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_index_buffer_32.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed_base.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed_dual_texture_full.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed_object_split.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed_simple_full.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed_simple_split.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed_smoke_full.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_indexed_static_dynamic.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_non_indexed.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/mesh_renderer_data.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/sprite_mesh.h
${BA_SRC_ROOT}/ballistica/graphics/mesh/text_mesh.cc
${BA_SRC_ROOT}/ballistica/graphics/mesh/text_mesh.h
${BA_SRC_ROOT}/ballistica/graphics/net_graph.cc
${BA_SRC_ROOT}/ballistica/graphics/net_graph.h
${BA_SRC_ROOT}/ballistica/graphics/render_command_buffer.h
${BA_SRC_ROOT}/ballistica/graphics/render_pass.cc
${BA_SRC_ROOT}/ballistica/graphics/render_pass.h
${BA_SRC_ROOT}/ballistica/graphics/render_target.cc
${BA_SRC_ROOT}/ballistica/graphics/render_target.h
${BA_SRC_ROOT}/ballistica/graphics/renderer.cc
${BA_SRC_ROOT}/ballistica/graphics/renderer.h
${BA_SRC_ROOT}/ballistica/graphics/text/font_page_map_data.h
${BA_SRC_ROOT}/ballistica/graphics/text/text_graphics.cc
${BA_SRC_ROOT}/ballistica/graphics/text/text_graphics.h
${BA_SRC_ROOT}/ballistica/graphics/text/text_group.cc
${BA_SRC_ROOT}/ballistica/graphics/text/text_group.h
${BA_SRC_ROOT}/ballistica/graphics/text/text_packer.cc
${BA_SRC_ROOT}/ballistica/graphics/text/text_packer.h
${BA_SRC_ROOT}/ballistica/graphics/texture/dds.cc
${BA_SRC_ROOT}/ballistica/graphics/texture/dds.h
${BA_SRC_ROOT}/ballistica/graphics/texture/ktx.cc
${BA_SRC_ROOT}/ballistica/graphics/texture/ktx.h
${BA_SRC_ROOT}/ballistica/graphics/texture/pvr.cc
${BA_SRC_ROOT}/ballistica/graphics/texture/pvr.h
${BA_SRC_ROOT}/ballistica/graphics/vr_graphics.cc
${BA_SRC_ROOT}/ballistica/graphics/vr_graphics.h
${BA_SRC_ROOT}/ballistica/input/device/client_input_device.cc
${BA_SRC_ROOT}/ballistica/input/device/client_input_device.h
${BA_SRC_ROOT}/ballistica/input/device/input_device.cc
${BA_SRC_ROOT}/ballistica/input/device/input_device.h
${BA_SRC_ROOT}/ballistica/input/device/joystick.cc
${BA_SRC_ROOT}/ballistica/input/device/joystick.h
${BA_SRC_ROOT}/ballistica/input/device/keyboard_input.cc
${BA_SRC_ROOT}/ballistica/input/device/keyboard_input.h
${BA_SRC_ROOT}/ballistica/input/device/test_input.cc
${BA_SRC_ROOT}/ballistica/input/device/test_input.h
${BA_SRC_ROOT}/ballistica/input/device/touch_input.cc
${BA_SRC_ROOT}/ballistica/input/device/touch_input.h
${BA_SRC_ROOT}/ballistica/input/input.cc
${BA_SRC_ROOT}/ballistica/input/input.h
${BA_SRC_ROOT}/ballistica/input/remote_app.cc
${BA_SRC_ROOT}/ballistica/input/remote_app.h
${BA_SRC_ROOT}/ballistica/input/std_input_module.cc
${BA_SRC_ROOT}/ballistica/input/std_input_module.h
${BA_SRC_ROOT}/ballistica/math/matrix44f.cc
${BA_SRC_ROOT}/ballistica/math/matrix44f.h
${BA_SRC_ROOT}/ballistica/math/point2d.h
${BA_SRC_ROOT}/ballistica/math/random.cc
${BA_SRC_ROOT}/ballistica/math/random.h
${BA_SRC_ROOT}/ballistica/math/rect.h
${BA_SRC_ROOT}/ballistica/math/vector2f.h
${BA_SRC_ROOT}/ballistica/math/vector3f.cc
${BA_SRC_ROOT}/ballistica/math/vector3f.h
${BA_SRC_ROOT}/ballistica/math/vector4f.h
${BA_SRC_ROOT}/ballistica/media/component/collide_model.cc
${BA_SRC_ROOT}/ballistica/media/component/collide_model.h
${BA_SRC_ROOT}/ballistica/media/component/cube_map_texture.cc
${BA_SRC_ROOT}/ballistica/media/component/cube_map_texture.h
${BA_SRC_ROOT}/ballistica/media/component/data.cc
${BA_SRC_ROOT}/ballistica/media/component/data.h
${BA_SRC_ROOT}/ballistica/media/component/media_component.cc
${BA_SRC_ROOT}/ballistica/media/component/media_component.h
${BA_SRC_ROOT}/ballistica/media/component/model.cc
${BA_SRC_ROOT}/ballistica/media/component/model.h
${BA_SRC_ROOT}/ballistica/media/component/sound.cc
${BA_SRC_ROOT}/ballistica/media/component/sound.h
${BA_SRC_ROOT}/ballistica/media/component/texture.cc
${BA_SRC_ROOT}/ballistica/media/component/texture.h
${BA_SRC_ROOT}/ballistica/media/data/collide_model_data.cc
${BA_SRC_ROOT}/ballistica/media/data/collide_model_data.h
${BA_SRC_ROOT}/ballistica/media/data/data_data.cc
${BA_SRC_ROOT}/ballistica/media/data/data_data.h
${BA_SRC_ROOT}/ballistica/media/data/media_component_data.cc
${BA_SRC_ROOT}/ballistica/media/data/media_component_data.h
${BA_SRC_ROOT}/ballistica/media/data/model_data.cc
${BA_SRC_ROOT}/ballistica/media/data/model_data.h
${BA_SRC_ROOT}/ballistica/media/data/model_renderer_data.h
${BA_SRC_ROOT}/ballistica/media/data/sound_data.cc
${BA_SRC_ROOT}/ballistica/media/data/sound_data.h
${BA_SRC_ROOT}/ballistica/media/data/texture_data.cc
${BA_SRC_ROOT}/ballistica/media/data/texture_data.h
${BA_SRC_ROOT}/ballistica/media/data/texture_preload_data.cc
${BA_SRC_ROOT}/ballistica/media/data/texture_preload_data.h
${BA_SRC_ROOT}/ballistica/media/data/texture_renderer_data.h
${BA_SRC_ROOT}/ballistica/media/media.cc
${BA_SRC_ROOT}/ballistica/media/media.h
${BA_SRC_ROOT}/ballistica/media/media_server.cc
${BA_SRC_ROOT}/ballistica/media/media_server.h
${BA_SRC_ROOT}/ballistica/networking/network_reader.h
${BA_SRC_ROOT}/ballistica/networking/network_write_module.h
${BA_SRC_ROOT}/ballistica/networking/networking.h
${BA_SRC_ROOT}/ballistica/networking/networking_sys.h
${BA_SRC_ROOT}/ballistica/networking/sockaddr.h
${BA_SRC_ROOT}/ballistica/networking/telnet_server.cc
${BA_SRC_ROOT}/ballistica/networking/telnet_server.h
${BA_SRC_ROOT}/ballistica/platform/apple/platform_apple.h
${BA_SRC_ROOT}/ballistica/platform/linux/platform_linux.cc
${BA_SRC_ROOT}/ballistica/platform/linux/platform_linux.h
${BA_SRC_ROOT}/ballistica/platform/min_sdl.h
${BA_SRC_ROOT}/ballistica/platform/platform.cc
${BA_SRC_ROOT}/ballistica/platform/platform.h
${BA_SRC_ROOT}/ballistica/platform/sdl/sdl_app.cc
${BA_SRC_ROOT}/ballistica/platform/sdl/sdl_app.h
${BA_SRC_ROOT}/ballistica/python/class/python_class.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_activity_data.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_activity_data.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_collide_model.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_collide_model.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_context.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_context.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_context_call.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_context_call.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_data.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_data.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_input_device.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_input_device.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_material.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_material.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_model.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_model.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_node.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_node.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_session_data.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_session_data.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_session_player.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_session_player.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_sound.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_sound.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_texture.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_texture.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_timer.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_timer.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_vec3.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_vec3.h
${BA_SRC_ROOT}/ballistica/python/class/python_class_widget.cc
${BA_SRC_ROOT}/ballistica/python/class/python_class_widget.h
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_app.cc
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_app.h
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_gameplay.cc
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_gameplay.h
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_graphics.cc
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_graphics.h
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_input.cc
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_input.h
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_media.cc
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_media.h
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_system.cc
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_system.h
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_ui.cc
${BA_SRC_ROOT}/ballistica/python/methods/python_methods_ui.h
${BA_SRC_ROOT}/ballistica/python/python.h
${BA_SRC_ROOT}/ballistica/python/python_command.cc
${BA_SRC_ROOT}/ballistica/python/python_command.h
${BA_SRC_ROOT}/ballistica/python/python_context_call.cc
${BA_SRC_ROOT}/ballistica/python/python_context_call.h
${BA_SRC_ROOT}/ballistica/python/python_context_call_runnable.h
${BA_SRC_ROOT}/ballistica/python/python_ref.cc
${BA_SRC_ROOT}/ballistica/python/python_ref.h
${BA_SRC_ROOT}/ballistica/python/python_sys.h
${BA_SRC_ROOT}/ballistica/scene/node/anim_curve_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/anim_curve_node.h
${BA_SRC_ROOT}/ballistica/scene/node/bomb_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/bomb_node.h
${BA_SRC_ROOT}/ballistica/scene/node/combine_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/combine_node.h
${BA_SRC_ROOT}/ballistica/scene/node/explosion_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/explosion_node.h
${BA_SRC_ROOT}/ballistica/scene/node/flag_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/flag_node.h
${BA_SRC_ROOT}/ballistica/scene/node/flash_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/flash_node.h
${BA_SRC_ROOT}/ballistica/scene/node/globals_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/globals_node.h
${BA_SRC_ROOT}/ballistica/scene/node/image_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/image_node.h
${BA_SRC_ROOT}/ballistica/scene/node/light_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/light_node.h
${BA_SRC_ROOT}/ballistica/scene/node/locator_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/locator_node.h
${BA_SRC_ROOT}/ballistica/scene/node/math_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/math_node.h
${BA_SRC_ROOT}/ballistica/scene/node/node.cc
${BA_SRC_ROOT}/ballistica/scene/node/node.h
${BA_SRC_ROOT}/ballistica/scene/node/node_attribute.cc
${BA_SRC_ROOT}/ballistica/scene/node/node_attribute.h
${BA_SRC_ROOT}/ballistica/scene/node/node_attribute_connection.cc
${BA_SRC_ROOT}/ballistica/scene/node/node_attribute_connection.h
${BA_SRC_ROOT}/ballistica/scene/node/node_type.h
${BA_SRC_ROOT}/ballistica/scene/node/null_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/null_node.h
${BA_SRC_ROOT}/ballistica/scene/node/player_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/player_node.h
${BA_SRC_ROOT}/ballistica/scene/node/prop_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/prop_node.h
${BA_SRC_ROOT}/ballistica/scene/node/region_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/region_node.h
${BA_SRC_ROOT}/ballistica/scene/node/scorch_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/scorch_node.h
${BA_SRC_ROOT}/ballistica/scene/node/session_globals_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/session_globals_node.h
${BA_SRC_ROOT}/ballistica/scene/node/shield_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/shield_node.h
${BA_SRC_ROOT}/ballistica/scene/node/sound_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/sound_node.h
${BA_SRC_ROOT}/ballistica/scene/node/spaz_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/spaz_node.h
${BA_SRC_ROOT}/ballistica/scene/node/terrain_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/terrain_node.h
${BA_SRC_ROOT}/ballistica/scene/node/text_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/text_node.h
${BA_SRC_ROOT}/ballistica/scene/node/texture_sequence_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/texture_sequence_node.h
${BA_SRC_ROOT}/ballistica/scene/node/time_display_node.cc
${BA_SRC_ROOT}/ballistica/scene/node/time_display_node.h
${BA_SRC_ROOT}/ballistica/scene/scene.cc
${BA_SRC_ROOT}/ballistica/scene/scene.h
${BA_SRC_ROOT}/ballistica/ui/console.cc
${BA_SRC_ROOT}/ballistica/ui/console.h
${BA_SRC_ROOT}/ballistica/ui/root_ui.cc
${BA_SRC_ROOT}/ballistica/ui/root_ui.h
${BA_SRC_ROOT}/ballistica/ui/ui.cc
${BA_SRC_ROOT}/ballistica/ui/ui.h
${BA_SRC_ROOT}/ballistica/ui/widget/button_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/button_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/check_box_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/check_box_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/column_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/column_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/container_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/container_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/h_scroll_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/h_scroll_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/image_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/image_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/root_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/root_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/row_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/row_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/scroll_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/scroll_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/stack_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/stack_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/text_widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/text_widget.h
${BA_SRC_ROOT}/ballistica/ui/widget/widget.cc
${BA_SRC_ROOT}/ballistica/ui/widget/widget.h
# AUTOGENERATED_PUBLIC_END
)

View File

@ -0,0 +1,51 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/audio/al_sys.h"
#include "ballistica/audio/audio_server.h"
#include "ballistica/generic/utils.h"
// Need to move away from OpenAL on Apple stuff.
#if __clang__
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#if BA_ENABLE_AUDIO
namespace ballistica {
void _check_al_error(const char* file, int line) {
if (g_audio_server->paused()) {
Log(Utils::BaseName(file) + ":" + std::to_string(line)
+ ": Checking OpenAL error while paused.");
}
ALenum al_err = alGetError();
if (al_err != AL_NO_ERROR) {
Log(Utils::BaseName(file) + ":" + std::to_string(line)
+ ": OpenAL Error: " + GetALErrorString(al_err) + ";");
}
}
auto GetALErrorString(ALenum err) -> const char* {
static char undefErrStr[128];
#define DO_AL_ERR_CASE(a) \
case a: \
return #a
switch (err) {
DO_AL_ERR_CASE(AL_INVALID_NAME);
DO_AL_ERR_CASE(AL_ILLEGAL_ENUM);
DO_AL_ERR_CASE(AL_INVALID_VALUE);
DO_AL_ERR_CASE(AL_ILLEGAL_COMMAND);
DO_AL_ERR_CASE(AL_OUT_OF_MEMORY);
default: {
snprintf(undefErrStr, sizeof(undefErrStr), "(unrecognized: 0x%X (%d))",
err, err);
return undefErrStr;
}
}
#undef DO_AL_ERR_CASE
}
} // namespace ballistica
#endif // BA_ENABLE_AUDIO

View File

@ -0,0 +1,41 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_AUDIO_AL_SYS_H_
#define BALLISTICA_AUDIO_AL_SYS_H_
#include <string>
#include "ballistica/ballistica.h"
#if BA_ENABLE_AUDIO
#if HAVE_FRAMEWORK_OPENAL
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
#define CHECK_AL_ERROR _check_al_error(__FILE__, __LINE__)
#if BA_DEBUG_BUILD
#define DEBUG_CHECK_AL_ERROR CHECK_AL_ERROR
#else
#define DEBUG_CHECK_AL_ERROR ((void)0)
#endif
namespace ballistica {
const int kAudioStreamBufferSize = 4096 * 8;
const int kAudioStreamBufferCount = 7;
// Some OpenAL Error handling utils.
auto GetALErrorString(ALenum err) -> const char*;
void _check_al_error(const char* file, int line);
} // namespace ballistica
#endif // BA_ENABLE_AUDIO
#endif // BALLISTICA_AUDIO_AL_SYS_H_

View File

@ -0,0 +1,180 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/audio/audio.h"
#include "ballistica/audio/audio_server.h"
#include "ballistica/audio/audio_source.h"
#include "ballistica/media/data/sound_data.h"
namespace ballistica {
Audio::Audio() { assert(InGameThread()); }
void Audio::Init() {
// Init our singleton.
assert(g_audio == nullptr);
g_audio = new Audio();
}
void Audio::Reset() {
assert(InGameThread());
g_audio_server->PushResetCall();
}
void Audio::SetVolumes(float music_volume, float sound_volume) {
g_audio_server->PushSetVolumesCall(music_volume, sound_volume);
}
void Audio::SetSoundPitch(float pitch) {
g_audio_server->PushSetSoundPitchCall(pitch);
}
void Audio::SetListenerPosition(const Vector3f& p) {
g_audio_server->PushSetListenerPositionCall(p);
}
void Audio::SetListenerOrientation(const Vector3f& forward,
const Vector3f& up) {
g_audio_server->PushSetListenerOrientationCall(forward, up);
}
// This stops a particular sound play ID only.
void Audio::PushSourceStopSoundCall(uint32_t play_id) {
g_audio_server->PushCall(
[this, play_id] { g_audio_server->StopSound(play_id); });
}
void Audio::PushSourceFadeOutCall(uint32_t play_id, uint32_t time) {
g_audio_server->PushCall(
[this, play_id, time] { g_audio_server->FadeSoundOut(play_id, time); });
}
auto Audio::SourceBeginNew() -> AudioSource* {
BA_DEBUG_FUNCTION_TIMER_BEGIN();
AudioSource* s = nullptr;
{
// Gotta make sure to hold this until we've locked the source.
// Otherwise theoretically the audio thread could make our source available
// again before we can use it.
std::lock_guard<std::mutex> lock(available_sources_mutex_);
// If there's an available source, reserve and return it.
auto i = available_sources_.begin();
if (i != available_sources_.end()) {
s = *i;
available_sources_.erase(i);
assert(s->available());
assert(s->client_queue_size() == 0);
s->set_available(false);
}
if (s) {
s->Lock(1);
assert(!s->available());
s->set_client_queue_size(s->client_queue_size() + 1);
}
}
BA_DEBUG_FUNCTION_TIMER_END_THREAD(20);
return s;
}
auto Audio::IsSoundPlaying(uint32_t play_id) -> bool {
uint32_t source_id = AudioServer::source_id_from_play_id(play_id);
assert(client_sources_.size() > source_id);
client_sources_[source_id]->Lock(2);
bool result = (client_sources_[source_id]->play_id() == play_id);
client_sources_[source_id]->Unlock();
return result;
}
auto Audio::SourceBeginExisting(uint32_t play_id, uint32_t debug_id)
-> AudioSource* {
BA_DEBUG_FUNCTION_TIMER_BEGIN();
uint32_t source_id = AudioServer::source_id_from_play_id(play_id);
// Ok, the audio thread fills in this source list,
// so theoretically a client could call this before the audio thread
// has set it up. However no one should be trying to get a playing
// sound unless they've already started playing one which implies
// everything was set up already. I think we're good.
assert(g_audio->client_sources_.size() > source_id);
// If this guy's still got the play id they're asking about, lock/return it.
client_sources_[source_id]->Lock(debug_id);
if (client_sources_[source_id]->play_id() == play_id) {
assert(!client_sources_[source_id]->available());
client_sources_[source_id]->set_client_queue_size(
client_sources_[source_id]->client_queue_size() + 1);
BA_DEBUG_FUNCTION_TIMER_END_THREAD(20);
return client_sources_[source_id];
}
// No-go; unlock and return empty-handed.
client_sources_[source_id]->Unlock();
BA_DEBUG_FUNCTION_TIMER_END_THREAD(20);
return nullptr;
}
auto Audio::ShouldPlay(SoundData* sound) -> bool {
millisecs_t time = GetRealTime();
assert(sound);
return (time - sound->last_play_time() > 50);
}
void Audio::PlaySound(SoundData* sound, float volume) {
assert(InGameThread());
BA_DEBUG_FUNCTION_TIMER_BEGIN();
assert(sound);
if (!ShouldPlay(sound)) {
return;
}
AudioSource* s = SourceBeginNew();
if (s) {
// In vr mode, play non-positional sounds positionally in space roughly
// where the menu is.
if (IsVRMode()) {
s->SetGain(volume);
s->SetPositional(true);
float x = 0.0f;
float y = 4.5f;
float z = -3.0f;
s->SetPosition(x, y, z);
s->Play(sound);
s->End();
} else {
s->SetGain(volume);
s->SetPositional(false);
s->Play(sound);
s->End();
}
}
BA_DEBUG_FUNCTION_TIMER_END(20);
}
void Audio::PlaySoundAtPosition(SoundData* sound, float volume, float x,
float y, float z) {
assert(sound);
if (!ShouldPlay(sound)) {
return;
}
// Run locally.
if (AudioSource* source = SourceBeginNew()) {
source->SetGain(volume);
source->SetPositional(true);
source->SetPosition(x, y, z);
source->Play(sound);
source->End();
}
}
void Audio::AddClientSource(AudioSource* source) {
client_sources_.push_back(source);
}
void Audio::MakeSourceAvailable(AudioSource* source) {
available_sources_.push_back(source);
}
} // namespace ballistica

View File

@ -0,0 +1,82 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_AUDIO_AUDIO_H_
#define BALLISTICA_AUDIO_AUDIO_H_
#include <map>
#include <mutex>
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/core/object.h"
namespace ballistica {
/// Client class for audio operations;
/// used by the game and/or other threads.
class Audio {
public:
static void Init();
void Reset();
void SetVolumes(float music_volume, float sound_volume);
void SetListenerPosition(const Vector3f& p);
void SetListenerOrientation(const Vector3f& forward, const Vector3f& up);
void SetSoundPitch(float pitch);
// Return a pointer to a locked sound source, or nullptr if they're all busy.
// The sound source will be reset to standard settings (no loop, fade 1, pos
// 0,0,0, etc).
// Send the source any immediate commands and then unlock it.
// For later modifications, re-retrieve the sound with GetPlayingSound()
auto SourceBeginNew() -> AudioSource*;
// If a sound play id is playing, locks and returns its sound source.
// on success, you must unlock the source once done with it.
auto SourceBeginExisting(uint32_t play_id, uint32_t debug_id) -> AudioSource*;
// Return true if the sound id is currently valid. This is not guaranteed
// to be super accurate, but can be used to determine if a sound is still
// playing.
auto IsSoundPlaying(uint32_t play_id) -> bool;
// Simple one-shot play functions.
void PlaySound(SoundData* s, float volume = 1.0f);
void PlaySoundAtPosition(SoundData* sound, float volume, float x, float y,
float z);
// Call this if you want to prevent repeated plays of the same sound. It'll
// tell you if the sound has been played recently. The one-shot sound-play
// functions use this under the hood. (PlaySound, PlaySoundAtPosition).
auto ShouldPlay(SoundData* s) -> bool;
// Hmm; shouldn't these be accessed through the Source class?
void PushSourceFadeOutCall(uint32_t play_id, uint32_t time);
void PushSourceStopSoundCall(uint32_t play_id);
void AddClientSource(AudioSource* source);
void MakeSourceAvailable(AudioSource* source);
auto available_sources_mutex() -> std::mutex& {
return available_sources_mutex_;
}
private:
Audio();
// Flat list of client sources indexed by id.
std::vector<AudioSource*> client_sources_;
// List of sources that are ready to use.
// This is kept filled by the audio thread
// and used by the client.
std::vector<AudioSource*> available_sources_;
// This must be locked whenever accessing the availableSources list.
std::mutex available_sources_mutex_;
};
} // namespace ballistica
#endif // BALLISTICA_AUDIO_AUDIO_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,142 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_AUDIO_AUDIO_SERVER_H_
#define BALLISTICA_AUDIO_AUDIO_SERVER_H_
#include <map>
#include <memory>
#include <vector>
#include "ballistica/core/module.h"
namespace ballistica {
/// A module that handles audio processing.
class AudioServer : public Module {
public:
static auto source_id_from_play_id(uint32_t play_id) -> uint32_t {
return play_id & 0xFFFFu;
}
static auto play_count_from_play_id(uint32_t play_id) -> uint32_t {
return play_id >> 16u;
}
explicit AudioServer(Thread* o);
void PushSetVolumesCall(float music_volume, float sound_volume);
void PushSetSoundPitchCall(float val);
void PushSetPausedCall(bool pause);
void HandleThreadPause() override;
void HandleThreadResume() override;
static void BeginInterruption();
static void EndInterruption();
void PushSetListenerPositionCall(const Vector3f& p);
void PushSetListenerOrientationCall(const Vector3f& forward,
const Vector3f& up);
void PushResetCall();
void PushHavePendingLoadsCall();
void PushComponentUnloadCall(
const std::vector<Object::Ref<MediaComponentData>*>& components);
/// For use by g_game_module().
void ClearSoundRefDeleteList();
auto paused() const -> bool { return paused_; }
private:
class ThreadSource;
~AudioServer() override;
// Client sources use these to pass settings to the server.
void PushSourceSetIsMusicCall(uint32_t play_id, bool val);
void PushSourceSetPositionalCall(uint32_t play_id, bool val);
void PushSourceSetPositionCall(uint32_t play_id, const Vector3f& p);
void PushSourceSetGainCall(uint32_t play_id, float val);
void PushSourceSetFadeCall(uint32_t play_id, float val);
void PushSourceSetLoopingCall(uint32_t play_id, bool val);
void PushSourcePlayCall(uint32_t play_id, Object::Ref<SoundData>* sound);
void PushSourceStopCall(uint32_t play_id);
void PushSourceEndCall(uint32_t play_id);
void SetPaused(bool paused);
// Fade a playing sound out over the given time. If it is already
// fading or does not exist, does nothing.
void FadeSoundOut(uint32_t play_id, uint32_t time);
// Stop a sound from playing if it exists.
void StopSound(uint32_t play_id);
void SetMusicVolume(float volume);
void SetSoundVolume(float volume);
void SetSoundPitch(float pitch);
auto music_volume() -> float { return music_volume_; }
auto sound_volume() -> float { return sound_volume_; }
auto sound_pitch() -> float { return sound_pitch_; }
/// If a sound play id is currently playing, return the sound.
auto GetPlayingSound(uint32_t play_id) -> ThreadSource*;
void Reset();
void Process();
/// Send a component to the audio thread to delete.
void DeleteMediaComponent(MediaComponentData* c);
void UpdateTimerInterval();
void UpdateAvailableSources();
void UpdateMusicPlayState();
void ProcessSoundFades();
// Some threads such as audio hold onto allocated Media-Component-Refs to keep
// media components alive that they need. Media-Component-Refs, however, must
// be disposed of in the game thread, so they are passed back to it through
// this function.
void AddSoundRefDelete(const Object::Ref<SoundData>* c);
Timer* process_timer_{};
bool have_pending_loads_{};
bool paused_{};
millisecs_t last_sound_fade_process_time_{};
#if BA_ENABLE_AUDIO
ALCcontext* alc_context_;
#endif
float sound_volume_{1.0f};
float sound_pitch_{1.0f};
float music_volume_{1.0f};
/// Indexed list of sources.
std::vector<ThreadSource*> sources_;
std::vector<ThreadSource*> streaming_sources_;
millisecs_t last_stream_process_time_{};
// Holds refs to all sources.
// Use sources, not this, for faster iterating.
std::vector<Object::Ref<ThreadSource> > sound_source_refs_;
struct SoundFadeNode;
std::map<int, SoundFadeNode> sound_fade_nodes_;
// This mutex controls access to our list of media component shared ptrs to
// delete in the main thread.
std::mutex sound_ref_delete_list_mutex_;
// Our list of sound media components to delete via the main thread.
std::vector<const Object::Ref<SoundData>*> sound_ref_delete_list_;
millisecs_t last_sanity_check_time_{};
static int al_source_count_;
// FIXME: Try to kill these.
friend class AudioSource;
friend class Audio;
};
} // namespace ballistica
#endif // BALLISTICA_AUDIO_AUDIO_SERVER_H_

View File

@ -0,0 +1,133 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/audio/audio_source.h"
#include <cmath>
#include "ballistica/audio/audio.h"
#include "ballistica/audio/audio_server.h"
#include "ballistica/math/vector3f.h"
#include "ballistica/media/data/sound_data.h"
namespace ballistica {
AudioSource::AudioSource(int id_in) : id_(id_in) {}
AudioSource::~AudioSource() { assert(client_queue_size_ == 0); }
void AudioSource::MakeAvailable(uint32_t play_id_new) {
assert(AudioServer::source_id_from_play_id(play_id_new) == id_);
assert(client_queue_size_ == 0);
assert(locked());
play_id_ = play_id_new;
assert(!available_);
assert(g_audio);
g_audio->MakeSourceAvailable(this);
available_ = true;
}
void AudioSource::SetIsMusic(bool val) {
assert(g_audio_server);
assert(client_queue_size_ > 0);
g_audio_server->PushSourceSetIsMusicCall(play_id_, val);
}
void AudioSource::SetPositional(bool val) {
assert(g_audio_server);
assert(client_queue_size_ > 0);
g_audio_server->PushSourceSetPositionalCall(play_id_, val);
}
void AudioSource::SetPosition(float x, float y, float z) {
assert(g_audio_server);
assert(client_queue_size_ > 0);
#if BA_DEBUG_BUILD
if (std::isnan(x) || std::isnan(y) || std::isnan(z)) {
Log("Error: Got nan value in AudioSource::SetPosition.");
}
#endif
g_audio_server->PushSourceSetPositionCall(play_id_, Vector3f(x, y, z));
}
void AudioSource::SetGain(float val) {
assert(g_audio_server);
assert(client_queue_size_ > 0);
g_audio_server->PushSourceSetGainCall(play_id_, val);
}
void AudioSource::SetFade(float val) {
assert(g_audio_server);
assert(client_queue_size_ > 0);
g_audio_server->PushSourceSetFadeCall(play_id_, val);
}
void AudioSource::SetLooping(bool val) {
assert(g_audio_server);
assert(client_queue_size_ > 0);
g_audio_server->PushSourceSetLoopingCall(play_id_, val);
}
auto AudioSource::Play(SoundData* ptr_in) -> uint32_t {
assert(ptr_in);
assert(g_audio_server);
assert(client_queue_size_ > 0);
// allocate a new reference to this guy and pass it along
// to the thread... (these refs can't be created or destroyed
// or have their ref-counts changed outside of the main thread...)
// the thread will then send back this allocated ptr when its done
// with it for the main thread to destroy.
ptr_in->UpdatePlayTime();
auto ptr = new Object::Ref<SoundData>(ptr_in);
g_audio_server->PushSourcePlayCall(play_id_, ptr);
return play_id_;
}
void AudioSource::Stop() {
assert(g_audio_server);
assert(client_queue_size_ > 0);
g_audio_server->PushSourceStopCall(play_id_);
}
void AudioSource::End() {
assert(client_queue_size_ > 0);
// send the thread a "this source is potentially free now" message
assert(g_audio_server);
g_audio_server->PushSourceEndCall(play_id_);
Unlock();
}
void AudioSource::Lock(int debug_id) {
BA_DEBUG_FUNCTION_TIMER_BEGIN();
mutex_.lock();
#if BA_DEBUG_BUILD
last_lock_time_ = GetRealTime();
lock_debug_id_ = debug_id;
locked_ = true;
#endif
BA_DEBUG_FUNCTION_TIMER_END_THREAD(20);
}
auto AudioSource::TryLock(int debug_id) -> bool {
bool locked = mutex_.try_lock();
#if (BA_DEBUG_BUILD || BA_TEST_BUILD)
if (locked) {
locked_ = true;
last_lock_time_ = GetRealTime();
lock_debug_id_ = debug_id;
}
#endif
return locked;
}
void AudioSource::Unlock() {
BA_DEBUG_FUNCTION_TIMER_BEGIN();
mutex_.unlock();
BA_DEBUG_FUNCTION_TIMER_END_THREAD(20);
#if BA_DEBUG_BUILD || BA_TEST_BUILD
locked_ = false;
#endif
}
} // namespace ballistica

View File

@ -0,0 +1,71 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_AUDIO_AUDIO_SOURCE_H_
#define BALLISTICA_AUDIO_AUDIO_SOURCE_H_
#include <mutex>
#include "ballistica/core/object.h"
namespace ballistica {
// Location for sound emission (client version)
class AudioSource {
public:
// Sets whether a source is "music".
// This mainly just influences which volume controls
// affect it.
void SetIsMusic(bool m);
// Sets whether a source is positional.
// A non-positional source's position coords are always
// relative to the listener. ie: 0,0,0 will always be centered.
void SetPositional(bool p);
void SetPosition(float x, float y, float z);
void SetGain(float g);
void SetFade(float f);
void SetLooping(bool loop);
auto Play(SoundData* ptr) -> uint32_t;
void Stop();
// Always call this when done sending commands to the source.
void End();
~AudioSource();
// Lock the source. Sources must be locked whenever calling any public func.
void Lock(int debug_id);
// Attempt to lock the source, but will not block. Returns true if
// successful.
auto TryLock(int debug_id) -> bool;
void Unlock();
explicit AudioSource(int id);
auto id() const -> int { return id_; }
#if BA_DEBUG_BUILD || BA_TEST_BUILD
auto last_lock_time() const -> millisecs_t { return last_lock_time_; }
auto lock_debug_id() const -> int { return lock_debug_id_; }
auto locked() const -> bool { return locked_; }
#endif
auto available() const -> bool { return available_; }
void set_available(bool val) { available_ = val; }
void MakeAvailable(uint32_t play_id);
auto client_queue_size() const -> int { return client_queue_size_; }
void set_client_queue_size(int val) { client_queue_size_ = val; }
auto play_id() const -> uint32_t { return play_id_; }
private:
std::mutex mutex_;
#if BA_DEBUG_BUILD || BA_TEST_BUILD
millisecs_t last_lock_time_ = 0;
int lock_debug_id_ = 0;
bool locked_ = false;
#endif
int client_queue_size_ = 0;
bool available_ = false;
int id_ = 0;
uint32_t play_id_ = 0;
};
} // namespace ballistica
#endif // BALLISTICA_AUDIO_AUDIO_SOURCE_H_

View File

@ -0,0 +1,145 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/audio/audio_streamer.h"
#include "ballistica/audio/audio.h"
#include "ballistica/audio/audio_server.h"
// Need to move away from OpenAL on Apple stuff.
#if __clang__
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
namespace ballistica {
#if BA_ENABLE_AUDIO
AudioStreamer::AudioStreamer(const char* file_name, ALuint source_in, bool loop)
: source_(source_in), file_name_(file_name), loops_(loop) {
assert(InAudioThread());
alGenBuffers(kAudioStreamBufferCount, buffers_);
CHECK_AL_ERROR;
}
AudioStreamer::~AudioStreamer() {
assert(!playing_);
assert(g_audio_server);
alDeleteBuffers(kAudioStreamBufferCount, buffers_);
CHECK_AL_ERROR;
}
auto AudioStreamer::Play() -> bool {
CHECK_AL_ERROR;
assert(!playing_);
playing_ = true;
// In case the source is already attached to something.
DetachBuffers();
// Fill all our buffers with data.
for (unsigned int buffer : buffers_) {
if (!Stream(buffer)) {
return false;
}
}
alSourceQueueBuffers(source_, kAudioStreamBufferCount, buffers_);
CHECK_AL_ERROR;
alSourcePlay(source_);
CHECK_AL_ERROR;
return true;
}
void AudioStreamer::Stop() {
CHECK_AL_ERROR;
assert(playing_);
alSourceStop(source_);
CHECK_AL_ERROR;
playing_ = false;
DetachBuffers();
DoStop();
}
void AudioStreamer::Update() {
if (eof_) return;
CHECK_AL_ERROR;
assert(playing_);
ALint queued;
ALint processed;
// See how many buffers have been processed.
alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
CHECK_AL_ERROR;
alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
CHECK_AL_ERROR;
// A fun anomaly in the linux version; we sometimes get more
// "processed" buffers than we have queued.
if (queued < processed) {
Log("Error: streamer oddness: queued(" + std::to_string(queued)
+ "); processed(" + std::to_string(processed) + ")");
processed = queued;
}
// Pull the completed ones off, refill them, and queue them back up.
while (processed--) {
ALuint buffer;
alSourceUnqueueBuffers(source_, 1, &buffer);
CHECK_AL_ERROR;
Stream(buffer);
if (!eof_) {
alSourceQueueBuffers(source_, 1, &buffer);
CHECK_AL_ERROR;
}
}
// Restart playback if need be.
ALenum state;
alGetSourcei(source_, AL_SOURCE_STATE, &state);
CHECK_AL_ERROR;
if (state != AL_PLAYING) {
printf("AudioServer::Streamer: restarting playback\n");
fflush(stdout);
alSourcePlay(source_);
CHECK_AL_ERROR;
}
}
void AudioStreamer::DetachBuffers() {
#if BA_DEBUG_BUILD
ALint state;
alGetSourcei(source_, AL_SOURCE_STATE, &state);
CHECK_AL_ERROR;
assert(state == AL_INITIAL || state == AL_STOPPED);
#endif
// This should clear everything.
alSourcei(source_, AL_BUFFER, 0);
CHECK_AL_ERROR;
}
auto AudioStreamer::Stream(ALuint buffer) -> bool {
char pcm[kAudioStreamBufferSize];
int size = 0;
unsigned int rate;
CHECK_AL_ERROR;
DoStream(pcm, &size, &rate);
if (size > 0) {
alBufferData(buffer, al_format(), pcm, size, rate);
CHECK_AL_ERROR;
} else {
eof_ = true;
}
return true;
}
#endif // BA_ENABLE_AUDIO
} // namespace ballistica

View File

@ -0,0 +1,62 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_AUDIO_AUDIO_STREAMER_H_
#define BALLISTICA_AUDIO_AUDIO_STREAMER_H_
#include <map>
#include <string>
#include "ballistica/audio/al_sys.h" // FIXME: shouldn't need this here.
#include "ballistica/core/object.h"
namespace ballistica {
#if BA_ENABLE_AUDIO
// Provider for streamed audio data.
class AudioStreamer : public Object {
public:
auto GetDefaultOwnerThread() const -> ThreadIdentifier override {
return ThreadIdentifier::kAudio;
}
AudioStreamer(const char* file_name, ALuint source, bool loop);
~AudioStreamer() override;
auto Play() -> bool;
void Stop();
void Update();
enum Format { INVALID_FORMAT, MONO16_FORMAT, STEREO16_FORMAT };
auto al_format() const -> ALenum {
switch (format_) {
case MONO16_FORMAT:
return AL_FORMAT_MONO16;
case STEREO16_FORMAT:
return AL_FORMAT_STEREO16;
default:
break;
}
return INVALID_FORMAT;
}
auto loops() const -> bool { return loops_; }
auto file_name() const -> const std::string& { return file_name_; }
protected:
virtual void DoStop() = 0;
virtual void DoStream(char* pcm, int* size, unsigned int* rate) = 0;
auto Stream(ALuint buffer) -> bool;
void DetachBuffers();
void set_format(Format format) { format_ = format; }
private:
Format format_ = INVALID_FORMAT;
bool playing_ = false;
ALuint buffers_[kAudioStreamBufferCount]{};
ALuint source_ = 0;
std::string file_name_;
bool loops_ = false;
bool eof_ = false;
};
#endif // BA_ENABLE_AUDIO
} // namespace ballistica
#endif // BALLISTICA_AUDIO_AUDIO_STREAMER_H_

View File

@ -0,0 +1,136 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/audio/ogg_stream.h"
#include <cstdio>
#include "ballistica/platform/platform.h"
namespace ballistica {
#if BA_ENABLE_AUDIO
static auto CallbackRead(void* ptr, size_t size, size_t nmemb,
void* data_source) -> size_t {
return fread(ptr, size, nmemb, static_cast<FILE*>(data_source));
}
static auto CallbackSeek(void* data_source, ogg_int64_t offset, int whence)
-> int {
return fseek(static_cast<FILE*>(data_source),
static_cast_check_fit<long>(offset), whence); // NOLINT
}
static auto CallbackClose(void* data_source) -> int {
return fclose(static_cast<FILE*>(data_source));
}
static long CallbackTell(void* data_source) { // NOLINT (ogg wants long)
return ftell(static_cast<FILE*>(data_source));
}
OggStream::OggStream(const char* file_name, ALuint source, bool loop)
: AudioStreamer(file_name, source, loop), have_ogg_file_(false) {
int result;
FILE* f;
if (!(f = g_platform->FOpen(file_name, "rb"))) {
throw Exception("can't open ogg file: '" + std::string(file_name) + "'");
}
ov_callbacks callbacks;
callbacks.read_func = CallbackRead;
callbacks.seek_func = CallbackSeek;
callbacks.close_func = CallbackClose;
callbacks.tell_func = CallbackTell;
// Have to use callbacks here as codewarrior's FILE struct doesn't
// seem to agree with what vorbis expects... oh well.
// Ericf note Aug 2019: Wow I have comments here old enough to be referencing
// codewarrior; that's awesome!
result = ov_open_callbacks(f, &ogg_file_, nullptr, 0, callbacks);
if (result < 0) {
fclose(f);
throw Exception(GetErrorString(result));
}
have_ogg_file_ = true;
vorbis_info_ = ov_info(&ogg_file_, -1);
if (vorbis_info_->channels == 1) {
set_format(MONO16_FORMAT);
} else {
set_format(STEREO16_FORMAT);
}
}
OggStream::~OggStream() {
if (have_ogg_file_) {
ov_clear(&ogg_file_);
}
}
void OggStream::DoStop() {
if (have_ogg_file_) ov_pcm_seek(&ogg_file_, 0);
}
void OggStream::DoStream(char* pcm, int* size, unsigned int* rate) {
int section;
int result;
while ((*size) < kAudioStreamBufferSize) {
// tremor's ov_read takes fewer args
#if (BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID)
result = static_cast<int>(ov_read(
&ogg_file_, pcm + (*size), kAudioStreamBufferSize - (*size), &section));
#else
result = static_cast<int>(ov_read(&ogg_file_, pcm + (*size),
kAudioStreamBufferSize - (*size), 0, 2, 1,
&section));
#endif // BA_OSTYPE_IOS_TVOS
if (result > 0) {
(*size) += result;
} else {
if (result < 0) {
static bool reported_error = false;
if (!reported_error) {
reported_error = true;
Log("Error streaming ogg file: '" + file_name() + "'.");
}
if (loops()) {
ov_pcm_seek(&ogg_file_, 0);
} else {
return;
}
} else {
// we hit the end of the file; either reset and keep reading if we're
// looping or just return what we got
if (loops()) {
ov_pcm_seek(&ogg_file_, 0);
} else {
return;
}
}
}
}
if ((*size) == 0 && loops()) {
throw Exception();
}
(*rate) = static_cast<unsigned int>(vorbis_info_->rate);
}
auto OggStream::GetErrorString(int code) -> std::string {
switch (code) {
case OV_EREAD:
return std::string("Read from media.");
case OV_ENOTVORBIS:
return std::string("Not Vorbis data.");
case OV_EVERSION:
return std::string("Vorbis version mismatch.");
case OV_EBADHEADER:
return std::string("Invalid Vorbis header.");
case OV_EFAULT:
return std::string("Internal logic fault (bug or heap/stack corruption.");
default:
return std::string("Unknown Ogg error.");
}
}
#endif // BA_ENABLE_AUDIO
} // namespace ballistica

View File

@ -0,0 +1,43 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_AUDIO_OGG_STREAM_H_
#define BALLISTICA_AUDIO_OGG_STREAM_H_
#include "ballistica/audio/audio_streamer.h"
#if BA_ENABLE_AUDIO
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
#include "ivorbisfile.h" // NOLINT
#else
#include <vorbis/vorbisfile.h>
#endif // BA_OSTYPE_IOS_TVOS
#endif // BA_ENABLE_AUDIO
#include <string>
namespace ballistica {
#if BA_ENABLE_AUDIO
// Handles streaming ogg audio.
class OggStream : public AudioStreamer {
public:
OggStream(const char* file_name, ALuint source, bool loop);
~OggStream() override;
protected:
void DoStop() override;
void DoStream(char* pcm, int* size, unsigned int* rate) override;
private:
auto GetErrorString(int code) -> std::string;
OggVorbis_File ogg_file_{};
bool have_ogg_file_;
vorbis_info* vorbis_info_;
};
#endif // BA_ENABLE_AUDIO
} // namespace ballistica
#endif // BALLISTICA_AUDIO_OGG_STREAM_H_

View File

@ -0,0 +1,376 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/bg/bg_dynamics.h"
#include <utility>
#include <vector>
#include "ballistica/core/thread.h"
#include "ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h"
#include "ballistica/dynamics/bg/bg_dynamics_fuse_data.h"
#include "ballistica/dynamics/bg/bg_dynamics_shadow_data.h"
#include "ballistica/dynamics/bg/bg_dynamics_volume_light_data.h"
#include "ballistica/graphics/component/object_component.h"
#include "ballistica/graphics/component/smoke_component.h"
#include "ballistica/graphics/component/sprite_component.h"
#include "ballistica/graphics/renderer.h"
#include "ballistica/media/component/collide_model.h"
namespace ballistica {
void BGDynamics::Init() {
// Just init our singleton.
new BGDynamics();
}
BGDynamics::BGDynamics() {
assert(InGameThread());
assert(g_bg_dynamics == nullptr);
g_bg_dynamics = this;
}
void BGDynamics::AddTerrain(CollideModelData* o) {
assert(InGameThread());
// Allocate a fresh reference to keep this collide-model alive as long as
// we're using it. Once we're done, we'll pass the pointer back to the
// main thread to free.
auto* model_ref = new Object::Ref<CollideModelData>(o);
g_bg_dynamics_server->PushAddTerrainCall(model_ref);
}
void BGDynamics::RemoveTerrain(CollideModelData* o) {
assert(InGameThread());
g_bg_dynamics_server->PushRemoveTerrainCall(o);
}
void BGDynamics::Emit(const BGDynamicsEmission& e) {
assert(InGameThread());
g_bg_dynamics_server->PushEmitCall(e);
}
// Call friend client to step our sim.
void BGDynamics::Step(const Vector3f& cam_pos) {
assert(InGameThread());
// The BG dynamics thread just processes steps as fast as it can;
// we need to throttle what we send or tell it to cut back if its behind
int step_count = g_bg_dynamics_server->step_count();
// If we're really getting behind, start pruning stuff.
if (step_count > 3) {
TooSlow();
}
// If we're slightly behind, just don't send this step;
// the bg dynamics will slow down a bit but nothing will disappear this way.
if (step_count > 1) return;
// Pass a newly allocated raw pointer to the bg-dynamics thread; it takes care
// of disposing it when done.
auto d = Object::NewDeferred<BGDynamicsServer::StepData>();
d->cam_pos = cam_pos;
{ // Shadows.
BA_DEBUG_TIME_CHECK_BEGIN(bg_dynamic_shadow_list_lock);
{
std::lock_guard<std::mutex> lock(
g_bg_dynamics_server->shadow_list_mutex_);
auto size = g_bg_dynamics_server->shadows_.size();
d->shadow_step_data_.resize(size);
if (size > 0) {
BGDynamicsShadowData** sd_client = &(g_bg_dynamics_server->shadows_[0]);
std::pair<BGDynamicsShadowData*, BGDynamicsServer::ShadowStepData>* sd =
&(d->shadow_step_data_[0]);
for (size_t i = 0; i < size; i++) {
// Set to nullptr (for ignore) if the client side is dead.
sd[i].first = sd_client[i]->client_dead ? nullptr : sd_client[i];
sd[i].second.position = sd_client[i]->pos_client;
}
}
}
BA_DEBUG_TIME_CHECK_END(bg_dynamic_shadow_list_lock, 10);
}
{ // Volume lights.
BA_DEBUG_TIME_CHECK_BEGIN(bg_dynamic_volumelights_list_lock);
{
std::lock_guard<std::mutex> lock(
g_bg_dynamics_server->volume_light_list_mutex_);
auto size = g_bg_dynamics_server->volume_lights_.size();
d->volume_light_step_data_.resize(size);
if (size > 0) {
BGDynamicsVolumeLightData** vd_client =
&(g_bg_dynamics_server->volume_lights_[0]);
std::pair<BGDynamicsVolumeLightData*,
BGDynamicsServer::VolumeLightStepData>* vd =
&(d->volume_light_step_data_[0]);
for (size_t i = 0; i < size; i++) {
// Set to nullptr (for ignore) if the client side is dead.
vd[i].first = vd_client[i]->client_dead ? nullptr : vd_client[i];
vd[i].second.pos = vd_client[i]->pos_client;
vd[i].second.radius = vd_client[i]->radius_client;
vd[i].second.r = vd_client[i]->r_client;
vd[i].second.g = vd_client[i]->g_client;
vd[i].second.b = vd_client[i]->b_client;
}
}
}
BA_DEBUG_TIME_CHECK_END(bg_dynamic_volumelights_list_lock, 10);
}
{ // Fuses.
BA_DEBUG_TIME_CHECK_BEGIN(bg_dynamic_fuse_list_lock);
{
std::lock_guard<std::mutex> lock(g_bg_dynamics_server->fuse_list_mutex_);
auto size = g_bg_dynamics_server->fuses_.size();
d->fuse_step_data_.resize(size);
if (size > 0) {
BGDynamicsFuseData** fd_client = &(g_bg_dynamics_server->fuses_[0]);
std::pair<BGDynamicsFuseData*, BGDynamicsServer::FuseStepData>* fd =
&(d->fuse_step_data_[0]);
for (size_t i = 0; i < size; i++) {
// Set to nullptr (for ignore) if the client side is dead.
fd[i].first = fd_client[i]->client_dead_ ? nullptr : fd_client[i];
fd[i].second.transform = fd_client[i]->transform_client_;
fd[i].second.have_transform = fd_client[i]->have_transform_client_;
fd[i].second.length = fd_client[i]->length_client_;
}
}
}
BA_DEBUG_TIME_CHECK_END(bg_dynamic_fuse_list_lock, 10);
}
// Increase our step count and ship it.
{
std::lock_guard<std::mutex> lock(g_bg_dynamics_server->step_count_mutex_);
g_bg_dynamics_server->step_count_++;
}
// Ok send the thread on its way.
g_bg_dynamics_server->PushStepCall(d);
}
void BGDynamics::SetDrawSnapshot(BGDynamicsDrawSnapshot* s) {
// We were passed a raw pointer; assign it to our unique_ptr which will
// take ownership of it and handle disposing it when we get the next one.
draw_snapshot_ = std::unique_ptr<BGDynamicsDrawSnapshot>(s);
}
void BGDynamics::TooSlow() {
if (!Thread::AreThreadsPaused()) {
g_bg_dynamics_server->PushTooSlowCall();
}
}
void BGDynamics::SetDebrisFriction(float val) {
assert(InGameThread());
g_bg_dynamics_server->PushSetDebrisFrictionCall(val);
}
void BGDynamics::SetDebrisKillHeight(float val) {
assert(InGameThread());
g_bg_dynamics_server->PushSetDebrisKillHeightCall(val);
}
void BGDynamics::Draw(FrameDef* frame_def) {
assert(InGameThread());
BGDynamicsDrawSnapshot* ds{draw_snapshot_.get()};
if (!ds) {
return;
}
// Draw sparks.
if (ds->spark_vertices.exists()) {
if (!sparks_mesh_.exists()) sparks_mesh_ = Object::New<SpriteMesh>();
sparks_mesh_->SetIndexData(ds->spark_indices);
sparks_mesh_->SetData(
Object::Ref<MeshBuffer<VertexSprite>>(ds->spark_vertices));
// In high-quality we draw in the overlay pass so we don't get wiped
// out by depth-of-field.
bool draw_in_overlay = (frame_def->quality() >= GraphicsQuality::kHigh);
SpriteComponent c(draw_in_overlay ? frame_def->overlay_3d_pass()
: frame_def->beauty_pass());
c.SetCameraAligned(true);
c.SetColor(2.0f, 2.0f, 2.0f, 1.0f);
c.SetOverlay(draw_in_overlay);
c.SetTexture(g_media->GetTexture(SystemTextureID::kSparks));
c.DrawMesh(sparks_mesh_.get(), kModelDrawFlagNoReflection);
c.Submit();
}
// Draw lights.
if (ds->light_vertices.exists()) {
assert(ds->light_indices.exists());
assert(!ds->light_indices->elements.empty());
assert(!ds->light_vertices->elements.empty());
if (!lights_mesh_.exists()) lights_mesh_ = Object::New<SpriteMesh>();
lights_mesh_->SetIndexData(ds->light_indices);
lights_mesh_->SetData(
Object::Ref<MeshBuffer<VertexSprite>>(ds->light_vertices));
SpriteComponent c(frame_def->light_shadow_pass());
c.SetTexture(g_media->GetTexture(SystemTextureID::kLightSoft));
c.DrawMesh(lights_mesh_.get());
c.Submit();
}
// Draw shadows.
if (ds->shadow_vertices.exists()) {
assert(ds->shadow_indices.exists());
if (!shadows_mesh_.exists()) shadows_mesh_ = Object::New<SpriteMesh>();
shadows_mesh_->SetIndexData(ds->shadow_indices);
shadows_mesh_->SetData(
Object::Ref<MeshBuffer<VertexSprite>>(ds->shadow_vertices));
SpriteComponent c(frame_def->light_shadow_pass());
c.SetTexture(g_media->GetTexture(SystemTextureID::kLight));
c.DrawMesh(shadows_mesh_.get());
c.Submit();
}
// Draw chunks.
DrawChunks(frame_def, &ds->rocks, BGDynamicsChunkType::kRock);
DrawChunks(frame_def, &ds->ice, BGDynamicsChunkType::kIce);
DrawChunks(frame_def, &ds->slime, BGDynamicsChunkType::kSlime);
DrawChunks(frame_def, &ds->metal, BGDynamicsChunkType::kMetal);
DrawChunks(frame_def, &ds->sparks, BGDynamicsChunkType::kSpark);
DrawChunks(frame_def, &ds->splinters, BGDynamicsChunkType::kSplinter);
DrawChunks(frame_def, &ds->sweats, BGDynamicsChunkType::kSweat);
DrawChunks(frame_def, &ds->flag_stands, BGDynamicsChunkType::kFlagStand);
// Draw tendrils.
if (ds->tendril_vertices.exists()) {
if (!tendrils_mesh_.exists())
tendrils_mesh_ = Object::New<MeshIndexedSmokeFull>();
tendrils_mesh_->SetIndexData(ds->tendril_indices);
tendrils_mesh_->SetData(
Object::Ref<MeshBuffer<VertexSmokeFull>>(ds->tendril_vertices));
bool draw_in_overlay = (frame_def->quality() >= GraphicsQuality::kHigh);
SmokeComponent c(draw_in_overlay ? frame_def->overlay_3d_pass()
: frame_def->beauty_pass());
c.SetOverlay(draw_in_overlay);
c.SetColor(1.0f, 1.0f, 1.0f, 1.0f);
c.DrawMesh(tendrils_mesh_.get(), kModelDrawFlagNoReflection);
c.Submit();
// Shadows.
if (frame_def->quality() >= GraphicsQuality::kHigher) {
for (auto&& i : ds->tendril_shadows) {
if (i.density > 0.0001f) {
Vector3f& p(i.p);
g_graphics->DrawBlotch(p, 2.0f * i.density, 0.02f * i.density,
0.01f * i.density, 0, 0.15f * i.density);
}
}
}
}
// Draw fuses.
if (ds->fuse_vertices.exists()) {
// Update our mesh with this data.
if (!fuses_mesh_.exists())
fuses_mesh_ = Object::New<MeshIndexedSimpleFull>();
fuses_mesh_->SetIndexData(ds->fuse_indices);
fuses_mesh_->SetData(
Object::Ref<MeshBuffer<VertexSimpleFull>>(ds->fuse_vertices));
{ // Draw!
ObjectComponent c(frame_def->beauty_pass());
c.SetTexture(g_media->GetTexture(SystemTextureID::kFuse));
c.DrawMesh(fuses_mesh_.get(), kModelDrawFlagNoReflection);
c.Submit();
}
}
}
void BGDynamics::DrawChunks(FrameDef* frame_def,
std::vector<Matrix44f>* draw_snapshot,
BGDynamicsChunkType chunk_type) {
if (!draw_snapshot || draw_snapshot->empty()) {
return;
}
// Draw ourself into the beauty pass.
ModelData* model;
switch (chunk_type) {
case BGDynamicsChunkType::kFlagStand:
model = g_media->GetModel(SystemModelID::kFlagStand);
break;
case BGDynamicsChunkType::kSplinter:
model = g_media->GetModel(SystemModelID::kShrapnelBoard);
break;
case BGDynamicsChunkType::kSlime:
model = g_media->GetModel(SystemModelID::kShrapnelSlime);
break;
default:
model = g_media->GetModel(SystemModelID::kShrapnel1);
break;
}
ObjectComponent c(frame_def->beauty_pass());
// Set up shading.
switch (chunk_type) {
case BGDynamicsChunkType::kRock: {
c.SetTexture(g_media->GetTexture(SystemTextureID::kShrapnel1));
c.SetReflection(ReflectionType::kSoft);
c.SetReflectionScale(0.2f, 0.2f, 0.2f);
c.SetColor(0.6f, 0.6f, 0.5f);
break;
}
case BGDynamicsChunkType::kIce: {
c.SetTexture(g_media->GetTexture(SystemTextureID::kShrapnel1));
c.SetReflection(ReflectionType::kSharp);
c.SetAddColor(0.5f, 0.5f, 0.9f);
break;
}
case BGDynamicsChunkType::kSlime: {
c.SetTexture(g_media->GetTexture(SystemTextureID::kShrapnel1));
c.SetReflection(ReflectionType::kSharper);
c.SetReflectionScale(3.0f, 3.0f, 3.0f);
c.SetColor(0.0f, 0.0f, 0.0f);
c.SetAddColor(0.6f, 0.7f, 0.08f);
break;
}
case BGDynamicsChunkType::kMetal: {
c.SetTexture(g_media->GetTexture(SystemTextureID::kShrapnel1));
c.SetReflection(ReflectionType::kPowerup);
c.SetColor(0.5f, 0.5f, 0.55f);
break;
}
case BGDynamicsChunkType::kSpark: {
c.SetTexture(g_media->GetTexture(SystemTextureID::kShrapnel1));
c.SetReflection(ReflectionType::kSharp);
c.SetColor(0.0f, 0.0f, 0.0f, 1.0f);
c.SetReflectionScale(4.0f, 3.0f, 2.0f);
c.SetAddColor(3.0f, 0.8f, 0.6f);
break;
}
case BGDynamicsChunkType::kSplinter: {
c.SetTexture(g_media->GetTexture(SystemTextureID::kShrapnel1));
c.SetReflection(ReflectionType::kSoft);
c.SetColor(1.0f, 0.8f, 0.5f);
break;
}
case BGDynamicsChunkType::kSweat: {
c.SetTransparent(true);
c.SetPremultiplied(true);
c.SetLightShadow(LightShadowType::kNone);
c.SetTexture(g_media->GetTexture(SystemTextureID::kShrapnel1));
c.SetReflection(ReflectionType::kSharp);
c.SetReflectionScale(0.5f, 0.4f, 0.3f);
c.SetColor(0.2f, 0.15f, 0.15f, 0.07f);
c.SetAddColor(0.05f, 0.05f, 0.01f);
break;
}
case BGDynamicsChunkType::kFlagStand: {
c.SetTexture(g_media->GetTexture(SystemTextureID::kFlagPole));
c.SetReflection(ReflectionType::kSharp);
c.SetColor(0.9f, 0.6f, 0.3f, 1.0f);
break;
}
default:
throw Exception();
}
c.DrawModelInstanced(model, *draw_snapshot, kModelDrawFlagNoReflection);
c.Submit();
}
} // namespace ballistica

View File

@ -0,0 +1,84 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_H_
#include <memory>
#include <vector>
#include "ballistica/core/object.h"
#include "ballistica/math/vector3f.h"
namespace ballistica {
enum class BGDynamicsEmitType {
kChunks,
kStickers,
kTendrils,
kDistortion,
kFlagStand,
kFairyDust
};
enum class BGDynamicsTendrilType { kSmoke, kThinSmoke, kIce };
enum class BGDynamicsChunkType {
kRock,
kIce,
kSlime,
kMetal,
kSpark,
kSplinter,
kSweat,
kFlagStand
};
class BGDynamicsEmission {
public:
BGDynamicsEmitType emit_type = BGDynamicsEmitType::kChunks;
Vector3f position{0.0f, 0.0f, 0.0f};
Vector3f velocity{0.0f, 0.0f, 0.0f};
int count{0};
float scale{1.0f};
float spread{1.0f};
BGDynamicsChunkType chunk_type{BGDynamicsChunkType::kRock};
BGDynamicsTendrilType tendril_type{BGDynamicsTendrilType::kSmoke};
};
// client (game thread) functionality for bg dynamics
class BGDynamics {
public:
static void Init();
void Emit(const BGDynamicsEmission& def);
void Step(const Vector3f& cam_pos);
// can be called to inform the bg dynamics thread to kill off some
// smoke/chunks/etc if rendering is chugging or whatnot.
void TooSlow();
// Draws the last snapshot the bg-dynamics-server has delivered to us
void Draw(FrameDef* frame_def);
void SetDebrisFriction(float val);
void SetDebrisKillHeight(float val);
void AddTerrain(CollideModelData* o);
void RemoveTerrain(CollideModelData* o);
// (sent to us by the bg dynamics server)
void SetDrawSnapshot(BGDynamicsDrawSnapshot* s);
private:
BGDynamics();
void DrawChunks(FrameDef* frame_def, std::vector<Matrix44f>* instances,
BGDynamicsChunkType chunk_type);
Object::Ref<SpriteMesh> lights_mesh_;
Object::Ref<SpriteMesh> shadows_mesh_;
Object::Ref<SpriteMesh> sparks_mesh_;
Object::Ref<MeshIndexedSmokeFull> tendrils_mesh_;
Object::Ref<MeshIndexedSimpleFull> fuses_mesh_;
std::unique_ptr<BGDynamicsDrawSnapshot> draw_snapshot_;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_H_

View File

@ -0,0 +1,79 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_DRAW_SNAPSHOT_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_DRAW_SNAPSHOT_H_
#include <vector>
#include "ballistica/graphics/renderer.h"
namespace ballistica {
// Big chunk of data sent back from the bg-dynamics server thread
// to the game thread for drawing.
class BGDynamicsDrawSnapshot {
public:
struct TendrilShadow {
TendrilShadow(const Vector3f& p_in, float density_in)
: p(p_in), density(density_in) {}
Vector3f p;
float density;
};
// These are created in the bg-dynamics thread, and object ownership
// needs to be switched back the the game-thread default when it is passed
// over or else the debug thread-access-checks will error.
void SetGameThreadOwnership() {
if (g_buildconfig.debug_build()) {
for (Object* o : {static_cast<Object*>(tendril_indices.get()),
static_cast<Object*>(tendril_vertices.get()),
static_cast<Object*>(fuse_indices.get()),
static_cast<Object*>(fuse_vertices.get()),
static_cast<Object*>(shadow_indices.get()),
static_cast<Object*>(shadow_vertices.get()),
static_cast<Object*>(light_indices.get()),
static_cast<Object*>(light_vertices.get()),
static_cast<Object*>(spark_indices.get()),
static_cast<Object*>(spark_vertices.get())}) {
if (o) {
o->SetThreadOwnership(Object::ThreadOwnership::kClassDefault);
}
}
}
}
// Particles.
std::vector<Matrix44f> rocks;
std::vector<Matrix44f> ice;
std::vector<Matrix44f> slime;
std::vector<Matrix44f> metal;
std::vector<Matrix44f> sparks;
std::vector<Matrix44f> splinters;
std::vector<Matrix44f> sweats;
std::vector<Matrix44f> flag_stands;
// Tendrils.
Object::Ref<MeshIndexBuffer16> tendril_indices;
Object::Ref<MeshBufferVertexSmokeFull> tendril_vertices;
std::vector<TendrilShadow> tendril_shadows;
// Fuses.
Object::Ref<MeshIndexBuffer16> fuse_indices;
Object::Ref<MeshBufferVertexSimpleFull> fuse_vertices;
// Shadows.
Object::Ref<MeshIndexBuffer16> shadow_indices;
Object::Ref<MeshBufferVertexSprite> shadow_vertices;
// Lights.
Object::Ref<MeshIndexBuffer16> light_indices;
Object::Ref<MeshBufferVertexSprite> light_vertices;
// Sparks.
Object::Ref<MeshIndexBuffer16> spark_indices;
Object::Ref<MeshBufferVertexSprite> spark_vertices;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_DRAW_SNAPSHOT_H_

View File

@ -0,0 +1,41 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/bg/bg_dynamics_fuse.h"
#include "ballistica/dynamics/bg/bg_dynamics_fuse_data.h"
namespace ballistica {
BGDynamicsFuse::BGDynamicsFuse() {
assert(g_bg_dynamics_server);
assert(InGameThread());
// Allocate our data. We'll pass this to the BGDynamics thread and
// it'll then own it.
data_ = new BGDynamicsFuseData();
g_bg_dynamics_server->PushAddFuseCall(data_);
}
BGDynamicsFuse::~BGDynamicsFuse() {
assert(g_bg_dynamics_server);
assert(InGameThread());
// Let the data know the client side is dead
// so we're no longer included in step messages.
// (since by the time the worker gets the the data will be gone).
data_->client_dead_ = true;
g_bg_dynamics_server->PushRemoveFuseCall(data_);
}
void BGDynamicsFuse::SetTransform(const Matrix44f& t) {
assert(InGameThread());
data_->transform_client_ = t;
data_->have_transform_client_ = true;
}
void BGDynamicsFuse::SetLength(float length) {
assert(InGameThread());
data_->length_client_ = length;
}
} // namespace ballistica

View File

@ -0,0 +1,24 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_FUSE_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_FUSE_H_
#include "ballistica/ballistica.h"
namespace ballistica {
// Client controlled fuse.
class BGDynamicsFuse {
public:
BGDynamicsFuse();
~BGDynamicsFuse();
void SetTransform(const Matrix44f& m);
void SetLength(float l);
private:
BGDynamicsFuseData* data_;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_FUSE_H_

View File

@ -0,0 +1,113 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_FUSE_DATA_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_FUSE_DATA_H_
#include <algorithm>
#include "ballistica/dynamics/bg/bg_dynamics_server.h"
namespace ballistica {
const int kFusePointCount = 4;
struct BGDynamicsFuseData {
void Synchronize() {
transform_worker_ = transform_client_;
have_transform_worker_ = have_transform_client_;
length_worker_ = length_client_;
}
void Update(BGDynamicsServer* dyn) {
// Do nothing if we havn't received an initial transform.
if (!have_transform_worker_) {
return;
}
seg_len_ = 0.2f * std::max(0.01f, length_worker_);
if (!initial_position_set_) {
// Snap all our stuff into place on the initial transform.
Vector3f pt = transform_worker_.GetTranslate();
target_pts_[0] = dyn_pts_[0] = pt;
auto up = Vector3f(&transform_worker_.m[4]);
for (int i = 1; i < kFusePointCount; i++) {
target_pts_[i] = target_pts_[i - 1] + up * seg_len_;
dyn_pts_[i] = target_pts_[i];
up = (target_pts_[i] - target_pts_[i - 1]).Normalized();
}
initial_position_set_ = true;
} else {
// ..otherwise dynamically update it.
Vector3f pt = transform_worker_.GetTranslate();
target_pts_[0] = dyn_pts_[0] = pt;
auto up = Vector3f(&transform_worker_.m[4]);
auto back = Vector3f(&transform_worker_.m[8]);
up = (up + -0.03f * back).Normalized();
float bAmt = 0.0f;
Vector3f oldTipPos = dyn_pts_[kFusePointCount - 1];
for (int i = 1; i < kFusePointCount; i++) {
target_pts_[i] = dyn_pts_[i - 1] + up * seg_len_;
float thisFollowAmt = (i == 1 ? 0.5f : 0.2f);
dyn_pts_[i] += thisFollowAmt * (target_pts_[i] - dyn_pts_[i]);
dyn_pts_[i] += Vector3f(0, -0.014f * 0.2f * length_worker_, 0);
up = (dyn_pts_[i] - dyn_pts_[i - 1] - bAmt * back).Normalized();
dyn_pts_[i] = dyn_pts_[i - 1] + up * seg_len_;
bAmt += 0.01f * length_worker_;
}
// Spit out a spark.
float r, g, b, a;
if (length_worker_ > 0.66f) {
r = 1.6f;
g = 1.5f;
b = 0.4f;
a = 0.5f;
} else if (length_worker_ > 0.33f) {
r = 2.0f;
g = 0.7f;
b = 0.3f;
a = 0.2f;
} else {
r = 3.0f;
g = 0.5f;
b = 0.4f;
a = 0.3f;
}
int count = 2;
if (dyn->graphics_quality() <= GraphicsQuality::kLow) {
count = 1;
}
for (int i = 0; i < count; i++) {
float rand_f = RandomFloat();
float d_life = -0.08f;
float d_size = 0.000f + 0.04f * rand_f * rand_f;
dyn->spark_particles()->Emit(dyn_pts_[kFusePointCount - 1],
dyn_pts_[kFusePointCount - 1] - oldTipPos,
r, g, b, a, d_life, 0.02f, d_size,
0.8f); // Flicker.
}
}
}
bool client_dead_{};
float seg_len_{};
Vector3f target_pts_[kFusePointCount]{};
Vector3f dyn_pts_[kFusePointCount]{};
float length_client_{1.0f};
float length_worker_{1.0f};
// Values owned by the client.
Matrix44f transform_client_{kMatrix44fIdentity};
// Values owned by the worker thread.
Matrix44f transform_worker_{kMatrix44fIdentity};
bool have_transform_client_{};
bool have_transform_worker_{};
bool initial_position_set_{};
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_FUSE_DATA_H_

View File

@ -0,0 +1,173 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/bg/bg_dynamics_height_cache.h"
#include <algorithm>
namespace ballistica {
const int kBGDynamicsHeightCacheMaxContacts = 20;
BGDynamicsHeightCache::BGDynamicsHeightCache()
: dirty_(true),
shadow_ray_(nullptr),
x_min_(-1.0f),
x_max_(1.0f),
y_min_(-1.0f),
y_max_(1.0f),
z_min_(-1.0f),
z_max_(1.0f) {
grid_width_ = 1;
grid_height_ = 1;
}
BGDynamicsHeightCache::~BGDynamicsHeightCache() {
if (shadow_ray_) {
dGeomDestroy(shadow_ray_);
}
}
auto BGDynamicsHeightCache::SampleCell(int x, int z) -> float {
int index = z * grid_width_ + x;
assert(index >= 0 && index < static_cast<int>(heights_.size())
&& index < static_cast<int>(heights_valid_.size()));
if (heights_valid_[index]) {
return heights_[index];
} else {
Vector3f p(
x_min_
+ ((static_cast<float>(x) + 0.5f) / static_cast<float>(grid_width_))
* (x_max_ - x_min_),
y_max_,
z_min_
+ ((static_cast<float>(z) + 0.5f)
/ static_cast<float>(grid_height_))
* (z_max_ - z_min_));
assert(shadow_ray_);
dGeomSetPosition(shadow_ray_, p.x, p.y, p.z);
float shadow_dist = y_max_ - y_min_;
for (auto& geom : geoms_) {
dContact contact[1];
if (dCollide(shadow_ray_, geom, kBGDynamicsHeightCacheMaxContacts,
&contact[0].geom, sizeof(dContact))) {
float len = p.y - contact[0].geom.pos[1];
if (len < shadow_dist) {
shadow_dist = len;
}
}
}
float height = y_max_ - shadow_dist;
heights_[index] = height;
heights_valid_[index] = 1;
return height;
}
}
auto BGDynamicsHeightCache::Sample(const Vector3f& pos) -> float {
if (dirty_) {
Update();
}
// Get sample point in grid coords.
float x =
static_cast<float>(grid_width_) * ((pos.x - x_min_) / (x_max_ - x_min_))
- 0.5f;
float z =
static_cast<float>(grid_height_) * ((pos.z - z_min_) / (z_max_ - z_min_))
- 0.5f;
// Sample the 4 contributing cells.
int x_min = static_cast<int>(floor(x));
x_min = std::max(0, std::min(grid_width_ - 1, x_min));
int x_max = static_cast<int>(ceil(x));
x_max = std::max(0, std::min(grid_width_ - 1, x_max));
float x_blend = fmod(x, 1.0f);
int z_min = static_cast<int>(floor(z));
z_min = std::max(0, std::min(grid_height_ - 1, z_min));
int z_max = static_cast<int>(ceil(z));
z_max = std::max(0, std::min(grid_height_ - 1, z_max));
float zBlend = fmod(z, 1.0f);
float xz = SampleCell(x_min, z_min);
float xZ = SampleCell(x_min, z_max);
float Xz = SampleCell(x_max, z_min);
float XZ = SampleCell(x_max, z_max);
// Weighted blend per row.
float zFin = xz * (1.0f - x_blend) + Xz * x_blend;
float ZFin = xZ * (1.0f - x_blend) + XZ * x_blend;
// Weighted blend of the two rows.
return zFin * (1.0f - zBlend) + ZFin * zBlend;
}
void BGDynamicsHeightCache::SetGeoms(const std::vector<dGeomID>& geoms) {
dirty_ = true;
geoms_ = geoms;
}
void BGDynamicsHeightCache::Update() {
// Calc our full dimensions.
if (geoms_.empty()) {
x_min_ = -1.0f;
x_max_ = 1.0f;
y_min_ = -1.0f;
y_max_ = 1.0f;
z_min_ = -1.0f;
z_max_ = 1.0f;
} else {
auto i = geoms_.begin();
dReal aabb[6];
dGeomGetAABB(*i, aabb);
float x = aabb[0];
float X = aabb[1];
float y = aabb[2];
float Y = aabb[3];
float z = aabb[4];
float Z = aabb[5];
for (i++; i != geoms_.end(); i++) {
dGeomGetAABB(*i, aabb);
if (aabb[0] < x) x = aabb[0];
if (aabb[1] > X) X = aabb[1];
if (aabb[2] < y) y = aabb[2];
if (aabb[3] > Y) Y = aabb[3];
if (aabb[4] < z) z = aabb[4];
if (aabb[5] > Z) Z = aabb[5];
}
float buffer = 0.3f;
x_min_ = x - buffer;
x_max_ = X + buffer;
y_min_ = y - buffer;
y_max_ = Y + buffer;
z_min_ = z - buffer;
z_max_ = Z + buffer;
}
// (Re)create our shadow ray with the new dimensions.
if (shadow_ray_) {
dGeomDestroy(shadow_ray_);
}
shadow_ray_ = dCreateRay(nullptr, y_max_ - y_min_);
dGeomRaySet(shadow_ray_, 0, 0, 0, 0, -1, 0); // Aim straight down.
dGeomRaySetClosestHit(shadow_ray_, true);
// Update/clear our cell grid based on our dimensions.
grid_width_ =
std::max(1, std::min(256, static_cast<int>((x_max_ - x_min_) * 8)));
grid_height_ =
std::max(1, std::min(256, static_cast<int>((z_max_ - z_min_) * 8)));
assert(grid_width_ >= 0 && grid_height_ >= 0);
auto cell_count_u = static_cast<uint32_t>(grid_width_ * grid_height_);
if (cell_count_u != heights_.size()) {
heights_.clear();
heights_.resize(cell_count_u);
heights_valid_.clear();
heights_valid_.resize(cell_count_u);
}
memset(&heights_valid_[0], 0, cell_count_u);
dirty_ = false;
}
} // namespace ballistica

View File

@ -0,0 +1,42 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_HEIGHT_CACHE_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_HEIGHT_CACHE_H_
#include <vector>
#include "ballistica/math/vector3f.h"
#include "ode/ode.h"
namespace ballistica {
// given geoms, creates/samples a height map on the fly
// for fast but not-perfectly-accurate height values
class BGDynamicsHeightCache {
public:
BGDynamicsHeightCache();
~BGDynamicsHeightCache();
auto Sample(const Vector3f& pos) -> float;
void SetGeoms(const std::vector<dGeomID>& geoms);
private:
auto SampleCell(int x, int y) -> float;
void Update();
std::vector<dGeomID> geoms_;
std::vector<float> heights_;
std::vector<uint8_t> heights_valid_;
bool dirty_;
dGeomID shadow_ray_;
int grid_width_;
int grid_height_;
float x_min_;
float x_max_;
float y_min_;
float y_max_;
float z_min_;
float z_max_;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_HEIGHT_CACHE_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,170 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SERVER_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SERVER_H_
#include <list>
#include <memory>
#include <utility>
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/dynamics/bg/bg_dynamics.h"
#include "ballistica/math/matrix44f.h"
#include "ballistica/math/vector3f.h"
#include "ode/ode.h"
namespace ballistica {
class BGDynamicsServer : public Module {
public:
struct Particle {
float x;
float y;
float z;
// Note that velocities here are in units-per-step (avoids a mult).
float vx;
float vy;
float vz;
float r;
float g;
float b;
float a;
float life;
float d_life;
float flicker;
float flicker_scale;
float size;
float d_size;
};
class ParticleSet {
public:
std::vector<Particle> particles[2];
int current_set;
ParticleSet() : current_set(0) {}
void Emit(const Vector3f& pos, const Vector3f& vel, float r, float g,
float b, float a, float dlife, float size, float d_size,
float flicker);
void UpdateAndCreateSnapshot(Object::Ref<MeshIndexBuffer16>* index_buffer,
Object::Ref<MeshBufferVertexSprite>* buffer);
};
struct ShadowStepData {
Vector3f position;
};
struct VolumeLightStepData {
Vector3f pos{};
float radius{};
float r{};
float g{};
float b{};
};
struct FuseStepData {
Matrix44f transform{};
bool have_transform{};
float length{};
};
class StepData : public Object {
public:
auto GetDefaultOwnerThread() const -> ThreadIdentifier override {
return ThreadIdentifier::kBGDynamics;
}
Vector3f cam_pos{0.0f, 0.0f, 0.0f};
// Basically a bit list of pointers to the current set of
// shadows/volumes/fuses and client values for them.
std::vector<std::pair<BGDynamicsShadowData*, ShadowStepData> >
shadow_step_data_;
std::vector<std::pair<BGDynamicsVolumeLightData*, VolumeLightStepData> >
volume_light_step_data_;
std::vector<std::pair<BGDynamicsFuseData*, FuseStepData> > fuse_step_data_;
};
explicit BGDynamicsServer(Thread* thread);
~BGDynamicsServer() override;
auto time() const -> uint32_t { return time_; }
auto graphics_quality() const -> GraphicsQuality { return graphics_quality_; }
void PushAddVolumeLightCall(BGDynamicsVolumeLightData* volume_light_data);
void PushRemoveVolumeLightCall(BGDynamicsVolumeLightData* volume_light_data);
void PushAddFuseCall(BGDynamicsFuseData* fuse_data);
void PushRemoveFuseCall(BGDynamicsFuseData* fuse_data);
void PushAddShadowCall(BGDynamicsShadowData* shadow_data);
void PushRemoveShadowCall(BGDynamicsShadowData* shadow_data);
void PushAddTerrainCall(Object::Ref<CollideModelData>* collide_model);
void PushRemoveTerrainCall(CollideModelData* collide_model);
void PushEmitCall(const BGDynamicsEmission& def);
auto spark_particles() const -> ParticleSet* {
return spark_particles_.get();
}
auto step_count() const -> int { return step_count_; }
private:
class Terrain;
class Chunk;
class Field;
class Tendril;
class TendrilController;
static void TerrainCollideCallback(void* data, dGeomID o1, dGeomID o2);
void Emit(const BGDynamicsEmission& def);
void PushStepCall(StepData* data);
void Step(StepData* data);
void PushTooSlowCall();
void PushSetDebrisFrictionCall(float friction);
void PushSetDebrisKillHeightCall(float height);
void Clear();
void UpdateFields();
void UpdateChunks();
void UpdateTendrils();
void UpdateFuses();
void UpdateShadows();
auto CreateDrawSnapshot() -> BGDynamicsDrawSnapshot*;
BGDynamicsChunkType cb_type_ = BGDynamicsChunkType::kRock;
dBodyID cb_body_{};
float cb_cfm_{0.0f};
float cb_erp_{0.0f};
// FIXME: We're assuming at the moment
// that collide-models passed to this thread never get deallocated. ew.
MeshIndexedSmokeFull* tendrils_smoke_mesh_{nullptr};
MeshIndexedSimpleFull* fuses_mesh_{nullptr};
SpriteMesh* shadows_mesh_{nullptr};
SpriteMesh* lights_mesh_{nullptr};
SpriteMesh* sparks_mesh_{nullptr};
int miss_count_{0};
Vector3f cam_pos_{0.0f, 0.0f, 0.0f};
std::vector<Terrain*> terrains_;
std::vector<BGDynamicsShadowData*> shadows_;
std::vector<BGDynamicsVolumeLightData*> volume_lights_;
std::vector<BGDynamicsFuseData*> fuses_;
dWorldID ode_world_{nullptr};
dJointGroupID ode_contact_group_{nullptr};
// Held by the dynamics module when changing any of these lists.
// Should be grabbed by a client if they need to access the list safely.
std::mutex shadow_list_mutex_;
std::mutex volume_light_list_mutex_;
std::mutex fuse_list_mutex_;
int step_count_{0};
std::mutex step_count_mutex_;
std::unique_ptr<ParticleSet> spark_particles_{nullptr};
std::list<Chunk*> chunks_;
std::list<Field*> fields_;
std::list<Tendril*> tendrils_;
int tendril_count_thick_{0};
int tendril_count_thin_{0};
int chunk_count_{0};
std::unique_ptr<BGDynamicsHeightCache> height_cache_;
std::unique_ptr<CollisionCache> collision_cache_;
uint32_t time_{0}; // Internal time step.
float debris_friction_{1.0f};
float debris_kill_height_{-50.0f};
GraphicsQuality graphics_quality_{GraphicsQuality::kLow};
friend class BGDynamics;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SERVER_H_

View File

@ -0,0 +1,53 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/bg/bg_dynamics_shadow.h"
#include "ballistica/dynamics/bg/bg_dynamics_server.h"
#include "ballistica/dynamics/bg/bg_dynamics_shadow_data.h"
#include "ballistica/graphics/graphics.h"
namespace ballistica {
BGDynamicsShadow::BGDynamicsShadow(float height_scaling) {
assert(InGameThread());
// allocate our shadow data.. we'll pass this to the BGDynamics thread
// and it'll then own it
data_ = new BGDynamicsShadowData(height_scaling);
assert(g_bg_dynamics_server);
g_bg_dynamics_server->PushAddShadowCall(data_);
}
BGDynamicsShadow::~BGDynamicsShadow() {
assert(InGameThread());
assert(g_bg_dynamics_server);
// let the data know the client side is dead
// so we're no longer included in step messages
// (since by the time the worker gets the the data will be gone)
data_->client_dead = true;
g_bg_dynamics_server->PushRemoveShadowCall(data_);
}
void BGDynamicsShadow::SetPosition(const Vector3f& pos) {
assert(InGameThread());
data_->pos_client = pos;
}
auto BGDynamicsShadow::GetPosition() const -> const Vector3f& {
assert(InGameThread());
return data_->pos_client;
}
void BGDynamicsShadow::GetValues(float* scale, float* density) const {
assert(InGameThread());
assert(scale);
assert(density);
*scale = data_->shadow_scale_client;
*density = data_->shadow_density_client
* g_graphics->GetShadowDensity(
data_->pos_client.x, data_->pos_client.y, data_->pos_client.z);
}
} // namespace ballistica

View File

@ -0,0 +1,36 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SHADOW_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SHADOW_H_
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/bg/bg_dynamics.h"
namespace ballistica {
// A utility class for client use which uses ray-testing and
// BG collision terrains to create a variably dense/soft shadow
// based on how high it is above terrain.
// Clients should give their current position information to the shadow
// at update time and then at render time it'll be all set to go.
// (shadows update in the bg dynamics stepping process)
class BGDynamicsShadow {
public:
explicit BGDynamicsShadow(float height_scaling = 1.0f);
~BGDynamicsShadow();
void SetPosition(const Vector3f& pos);
auto GetPosition() const -> const Vector3f&;
// Return scale and density for the shadow.
// this also takes into account the height based shadow density
// (g_graphics->GetShadowDensity()) so you don't have to.
void GetValues(float* scale, float* density) const;
private:
BGDynamicsShadowData* data_{};
BA_DISALLOW_CLASS_COPIES(BGDynamicsShadow);
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SHADOW_H_

View File

@ -0,0 +1,46 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SHADOW_DATA_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SHADOW_DATA_H_
namespace ballistica {
struct BGDynamicsShadowData {
explicit BGDynamicsShadowData(float height_scaling)
: height_scaling(height_scaling) {}
void UpdateClientData() {
// Copy data over with a bit of smoothing
// (so our shadow doesn't jump instantly when we go over and edge/etc.)
float smoothing{0.8f};
shadow_scale_client = smoothing * shadow_scale_client
+ (1.0f - smoothing) * shadow_scale_worker;
shadow_density_client = smoothing * shadow_density_client
+ (1.0f - smoothing) * shadow_density_worker;
}
void Synchronize() { pos_worker = pos_client; }
bool client_dead{};
float height_scaling{};
// For use by worker:
// position value owned by the client (write-only).
Vector3f pos_client{0.0f, 0.0f, 0.0f};
// Position value owned by the worker thread (read-only).
Vector3f pos_worker{0.0f, 0.0f, 0.0f};
// Calculated values owned by the worker thread (write-only).
float shadow_scale_worker{1.0f};
float shadow_density_worker{0.0f};
// Result values owned by the client (read-only).
float shadow_scale_client{1.0f};
float shadow_density_client{0.0f};
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_SHADOW_DATA_H_

View File

@ -0,0 +1,47 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/bg/bg_dynamics_volume_light.h"
#include "ballistica/dynamics/bg/bg_dynamics_volume_light_data.h"
namespace ballistica {
BGDynamicsVolumeLight::BGDynamicsVolumeLight() {
assert(InGameThread());
// allocate our light data.. we'll pass this to the BGDynamics thread
// and it'll then own it
data_ = new BGDynamicsVolumeLightData();
assert(g_bg_dynamics_server);
g_bg_dynamics_server->PushAddVolumeLightCall(data_);
}
BGDynamicsVolumeLight::~BGDynamicsVolumeLight() {
assert(InGameThread());
// let the data know the client side is dead
// so we're no longer included in step messages
// (since by the time the worker gets the the data will be gone)
data_->client_dead = true;
assert(g_bg_dynamics_server);
g_bg_dynamics_server->PushRemoveVolumeLightCall(data_);
}
void BGDynamicsVolumeLight::SetPosition(const Vector3f& pos) {
assert(InGameThread());
data_->pos_client = pos;
}
void BGDynamicsVolumeLight::SetRadius(float radius) {
assert(InGameThread());
data_->radius_client = radius;
}
void BGDynamicsVolumeLight::SetColor(float r, float g, float b) {
assert(InGameThread());
data_->r_client = r;
data_->g_client = g;
data_->b_client = b;
}
} // namespace ballistica

View File

@ -0,0 +1,25 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_VOLUME_LIGHT_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_VOLUME_LIGHT_H_
#include "ballistica/core/object.h"
namespace ballistica {
// Client-controlled lights for bg smoke.
class BGDynamicsVolumeLight : public Object {
public:
BGDynamicsVolumeLight();
~BGDynamicsVolumeLight() override;
void SetPosition(const Vector3f& pos);
void SetRadius(float radius);
void SetColor(float r, float g, float b);
private:
BGDynamicsVolumeLightData* data_;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_VOLUME_LIGHT_H_

View File

@ -0,0 +1,30 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_VOLUME_LIGHT_DATA_H_
#define BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_VOLUME_LIGHT_DATA_H_
#include "ballistica/dynamics/bg/bg_dynamics_server.h"
namespace ballistica {
struct BGDynamicsVolumeLightData {
bool client_dead{};
// Position value owned by the client.
Vector3f pos_client{0.0f, 0.0f, 0.0f};
float radius_client{};
float r_client{};
float g_client{};
float b_client{};
// Position value owned by the worker thread.
Vector3f pos_worker{0.0f, 0.0f, 0.0f};
float radius_worker{};
float r_worker{};
float g_worker{};
float b_worker{};
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_BG_BG_DYNAMICS_VOLUME_LIGHT_DATA_H_

View File

@ -0,0 +1,44 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_COLLISION_H_
#define BALLISTICA_DYNAMICS_COLLISION_H_
#include <vector>
#include "ballistica/ballistica.h"
#include "ballistica/core/object.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ode/ode.h"
namespace ballistica {
// Stores info about an occurring collision.
// Note than just because a collision exists between two parts doesn't mean
// they're physically colliding in the simulation. It is just a shortcut to
// determine what behavior, if any, exists between two parts which are currently
// overlapping in the simulation.
class Collision : public Object {
public:
explicit Collision(Scene* scene) : src_context(scene), dst_context(scene) {}
int claim_count{}; // Used when checking for out-of-date-ness.
bool collide{true};
int contact_count{}; // Current number of contacts.
float depth{}; // Current collision depth.
float x{};
float y{};
float z{};
float impact{};
float skid{};
float roll{};
Object::WeakRef<Part> src_part; // Ref to make sure still alive.
Object::WeakRef<Part> dst_part; // Ref to make sure still alive.
int body_id_1{-1};
int body_id_2{-1};
std::vector<dJointFeedback> collide_feedback;
MaterialContext src_context;
MaterialContext dst_context;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_COLLISION_H_

View File

@ -0,0 +1,355 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/collision_cache.h"
#include <algorithm>
#include "ballistica/graphics/component/simple_component.h"
#include "ode/ode_collision_kernel.h"
#include "ode/ode_collision_space_internal.h"
#include "ode/ode_collision_util.h"
#include "ode/ode_objects_private.h"
namespace ballistica {
CollisionCache::CollisionCache()
: dirty_(true),
shadow_ray_(nullptr),
x_min_(-1),
x_max_(1),
y_min_(-1),
y_max_(1),
z_min_(-1),
z_max_(1) {
grid_width_ = 1;
grid_height_ = 1;
test_box_ = dCreateBox(nullptr, 1, 1, 1);
}
CollisionCache::~CollisionCache() {
if (shadow_ray_) {
dGeomDestroy(shadow_ray_);
}
dGeomDestroy(test_box_);
}
void CollisionCache::SetGeoms(const std::vector<dGeomID>& geoms) {
dirty_ = true;
geoms_ = geoms;
}
void CollisionCache::Draw(FrameDef* frame_def) {
if (cells_.empty()) {
return;
}
SimpleComponent c(frame_def->beauty_pass());
c.SetTransparent(true);
c.SetColor(0, 1, 0, 0.1f);
float cell_width = (1.0f / static_cast<float>(grid_width_));
float cell_height = (1.0f / static_cast<float>(grid_height_));
c.PushTransform();
c.Translate((x_min_ + x_max_) * 0.5f, 0, (z_min_ + z_max_) * 0.5f);
c.Scale(x_max_ - x_min_, 1, z_max_ - z_min_);
c.PushTransform();
c.Scale(1, 0.01f, 1);
c.DrawModel(g_media->GetModel(SystemModelID::kBox));
c.PopTransform();
c.Translate(-0.5f + 0.5f * cell_width, 0, -0.5f + 0.5f * cell_height);
for (int x = 0; x < grid_width_; x++) {
for (int z = 0; z < grid_height_; z++) {
int cell_index = z * grid_width_ + x;
assert(cell_index >= 0 && cell_index < static_cast<int>(glow_.size()));
if (glow_[cell_index]) {
c.SetColor(1, 1, 1, 0.2f);
} else {
c.SetColor(0, 0, 1, 0.2f);
}
c.PushTransform();
c.Translate(static_cast<float>(x) / static_cast<float>(grid_width_),
cells_[cell_index].height_confirmed_collide_,
static_cast<float>(z) / static_cast<float>(grid_height_));
c.Scale(0.95f * cell_width, 0.01f, 0.95f * cell_height);
c.DrawModel(g_media->GetModel(SystemModelID::kBox));
c.PopTransform();
if (glow_[cell_index]) {
c.SetColor(1, 1, 1, 0.2f);
} else {
c.SetColor(1, 0, 0, 0.2f);
}
c.PushTransform();
c.Translate(static_cast<float>(x) / static_cast<float>(grid_width_),
cells_[cell_index].height_confirmed_empty_,
static_cast<float>(z) / static_cast<float>(grid_height_));
c.Scale(0.95f * cell_width, 0.01f, 0.95f * cell_height);
c.DrawModel(g_media->GetModel(SystemModelID::kBox));
c.PopTransform();
glow_[cell_index] = 0;
}
}
c.PopTransform();
c.Submit();
if (explicit_bool(false)) {
SimpleComponent c2(frame_def->overlay_3d_pass());
c2.SetTransparent(true);
c2.SetColor(1, 0, 0, 1.0f);
float cell_width2 = (1.0f / static_cast<float>(grid_width_));
float cell_height2 = (1.0f / static_cast<float>(grid_height_));
c2.PushTransform();
c2.Translate((x_min_ + x_max_) * 0.5f, 0, (z_min_ + z_max_) * 0.5f);
c2.Scale(x_max_ - x_min_, 1, z_max_ - z_min_);
c2.PushTransform();
c2.Scale(1, 0.01f, 1);
c2.DrawModel(g_media->GetModel(SystemModelID::kBox));
c2.PopTransform();
c2.Translate(-0.5f + 0.5f * cell_width2, 0, -0.5f + 0.5f * cell_height2);
for (int x = 0; x < grid_width_; x++) {
for (int z = 0; z < grid_height_; z++) {
int cell_index = z * grid_width_ + x;
assert(cell_index >= 0 && cell_index < static_cast<int>(glow_.size()));
if (glow_[cell_index]) {
c2.SetColor(1, 1, 1, 0.2f);
} else {
c2.SetColor(1, 0, 0, 0.2f);
}
c2.PushTransform();
c2.Translate(static_cast<float>(x) / static_cast<float>(grid_width_),
cells_[cell_index].height_confirmed_empty_,
static_cast<float>(z) / static_cast<float>(grid_height_));
c2.Scale(0.95f * cell_width2, 0.01f, 0.95f * cell_height2);
c2.DrawModel(g_media->GetModel(SystemModelID::kBox));
c2.PopTransform();
if (glow_[cell_index]) {
c2.SetColor(1, 1, 1, 0.2f);
} else {
c2.SetColor(0, 0, 1, 0.2f);
}
c2.PushTransform();
c2.Translate(static_cast<float>(x) / static_cast<float>(grid_width_),
cells_[cell_index].height_confirmed_collide_,
static_cast<float>(z) / static_cast<float>(grid_height_));
c2.Scale(0.95f * cell_width2, 0.01f, 0.95f * cell_height2);
c2.DrawModel(g_media->GetModel(SystemModelID::kBox));
c2.PopTransform();
glow_[cell_index] = 0;
}
}
c2.PopTransform();
c2.Submit();
}
}
void CollisionCache::Precalc() {
Update();
if (precalc_index_ >= cells_.size()) {
precalc_index_ = 0; // Loop.
}
auto x = static_cast<int>(precalc_index_ % grid_width_);
auto z = static_cast<int>(precalc_index_ / grid_width_);
assert(x >= 0 && x < grid_width_);
assert(z >= 0 && z < grid_height_);
TestCell(precalc_index_++, x, z);
}
void CollisionCache::CollideAgainstGeom(dGeomID g1, void* data,
dNearCallback* callback) {
// Update bounds, test for quick out against our height map,
// and proceed to a full test on a positive result.
g1->recomputeAABB();
if (dirty_) Update();
// Do a quick out if its not within our cache bounds at all.
dReal* bounds1 = g1->aabb;
if (bounds1[0] > x_max_ || bounds1[1] < x_min_ || bounds1[2] > y_max_
|| bounds1[3] < y_min_ || bounds1[4] > z_max_ || bounds1[5] < z_min_) {
return;
}
int x_min = static_cast<int>(static_cast<float>(grid_width_)
* ((g1->aabb[0] - x_min_) / (x_max_ - x_min_)));
x_min = std::max(0, std::min(grid_width_ - 1, x_min));
int z_min = static_cast<int>(static_cast<float>(grid_height_)
* ((g1->aabb[4] - z_min_) / (z_max_ - z_min_)));
z_min = std::max(0, std::min(grid_height_ - 1, z_min));
int x_max = static_cast<int>(static_cast<float>(grid_width_)
* ((g1->aabb[1] - x_min_) / (x_max_ - x_min_)));
x_max = std::max(0, std::min(grid_width_ - 1, x_max));
int z_max = static_cast<int>(static_cast<float>(grid_height_)
* ((g1->aabb[5] - z_min_) / (z_max_ - z_min_)));
z_max = std::max(0, std::min(grid_height_ - 1, z_max));
// If all cells are confirmed empty to the bottom of our AABB, we're done.
bool possible_hit = false;
for (int z = z_min; z <= z_max; z++) {
auto cell_index = static_cast<uint32_t>(z * grid_width_);
for (int x = x_min; x <= x_max; x++) {
if (bounds1[2] <= cells_[cell_index + x].height_confirmed_empty_) {
possible_hit = true;
break;
}
}
if (possible_hit) {
break;
}
}
if (!possible_hit) {
return;
}
// Ok looks like we need to run collisions.
int t_count = static_cast<int>(geoms_.size());
for (int i = 0; i < t_count; i++) {
dxGeom* g2 = geoms_[i];
collideAABBs(g1, g2, data, callback);
}
// While we're here, lets run one pass of tests on these cells to zero in on
// the actual collide/empty cutoff.
for (int z = z_min; z <= z_max; z++) {
int base_index = z * grid_width_;
for (int x = x_min; x <= x_max; x++) {
int cell_index = base_index + x;
assert(cell_index >= 0);
TestCell(static_cast<uint32_t>(cell_index), x, z);
}
}
}
void CollisionCache::TestCell(size_t cell_index, int x, int z) {
int t_count = static_cast<int>(geoms_.size());
float top = cells_[cell_index].height_confirmed_empty_;
// Midway point.
float bottom = (cells_[cell_index].height_confirmed_collide_ + top) * 0.5f;
float height = top - bottom;
// Don't wanna test with too thin a box.. may miss stuff.
float box_height = std::max(1.0f, height);
if (height > 0.01f) {
glow_[cell_index] = 1;
dGeomSetPosition(test_box_,
x_min_ + cell_width_ * (0.5f + static_cast<float>(x)),
bottom + box_height * 0.5f,
z_min_ + cell_height_ * (0.5f + static_cast<float>(z)));
dGeomBoxSetLengths(test_box_, cell_width_, box_height, cell_height_);
dContact contact[1];
bool collided = false;
// See if we collide with *any* terrain.
for (int i = 0; i < t_count; i++) {
if (dCollide(test_box_, geoms_[i], 1, &contact[0].geom,
sizeof(dContact))) {
collided = true;
break;
}
}
// Ok, we collided. We can move our confirmed collide floor up to
// our bottom.
if (collided) {
cells_[cell_index].height_confirmed_collide_ =
std::max(cells_[cell_index].height_confirmed_collide_, bottom);
} else {
// Didn't collide. Move confirmed empty region to our bottom.
cells_[cell_index].height_confirmed_empty_ =
std::min(cells_[cell_index].height_confirmed_empty_, bottom);
}
// This shouldn' happen but just in case.
cells_[cell_index].height_confirmed_empty_ =
std::max(cells_[cell_index].height_confirmed_empty_,
cells_[cell_index].height_confirmed_collide_);
}
}
void CollisionCache::CollideAgainstSpace(dSpaceID space, void* data,
dNearCallback* callback) {
// We handle our own testing against trimeshes so we can bring our fancy
// caching into play.
if (!geoms_.empty()) {
// Intersect all geoms in the space against all terrains.
for (dxGeom* g1 = space->first; g1; g1 = g1->next) {
CollideAgainstGeom(g1, data, callback);
}
}
}
void CollisionCache::Update() {
if (!dirty_) {
return;
}
// Calc our full dimensions.
if (geoms_.empty()) {
x_min_ = -1;
x_max_ = 1;
y_min_ = -1;
y_max_ = 1;
z_min_ = -1;
z_max_ = 1;
} else {
auto i = geoms_.begin();
dReal aabb[6];
dGeomGetAABB(*i, aabb);
float x = aabb[0];
float X = aabb[1];
float y = aabb[2];
float Y = aabb[3];
float z = aabb[4];
float Z = aabb[5];
for (i++; i != geoms_.end(); i++) {
dGeomGetAABB(*i, aabb);
if (aabb[0] < x) x = aabb[0];
if (aabb[1] > X) X = aabb[1];
if (aabb[2] < y) y = aabb[2];
if (aabb[3] > Y) Y = aabb[3];
if (aabb[4] < z) z = aabb[4];
if (aabb[5] > Z) Z = aabb[5];
}
float buffer = 0.3f;
x_min_ = x - buffer;
x_max_ = X + buffer;
y_min_ = y - buffer;
y_max_ = Y + buffer;
z_min_ = z - buffer;
z_max_ = Z + buffer;
}
// (Re)create our shadow ray with the new dimensions.
if (shadow_ray_) {
dGeomDestroy(shadow_ray_);
}
shadow_ray_ = dCreateRay(nullptr, y_max_ - y_min_);
dGeomRaySet(shadow_ray_, 0, 0, 0, 0, -1, 0); // aim straight down
dGeomRaySetClosestHit(shadow_ray_, true);
// Update/clear our cell grid based on our dimensions.
grid_width_ =
std::max(1, std::min(256, static_cast<int>((x_max_ - x_min_) * 1.3f)));
grid_height_ =
std::max(1, std::min(256, static_cast<int>((z_max_ - z_min_) * 1.3f)));
assert(grid_width_ >= 0 && grid_height_ >= 0);
auto cell_count = static_cast<uint32_t>(grid_width_ * grid_height_);
cells_.clear();
cells_.resize(cell_count);
for (uint32_t i = 0; i < cell_count; i++) {
cells_[i].height_confirmed_empty_ = y_max_;
cells_[i].height_confirmed_collide_ = y_min_;
}
cell_width_ = (x_max_ - x_min_) / static_cast<float>(grid_width_);
cell_height_ = (z_max_ - z_min_) / static_cast<float>(grid_height_);
glow_.clear();
glow_.resize(cell_count);
memset(&glow_[0], 0, cell_count);
precalc_index_ = 0;
dirty_ = false;
}
} // namespace ballistica

View File

@ -0,0 +1,58 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_COLLISION_CACHE_H_
#define BALLISTICA_DYNAMICS_COLLISION_CACHE_H_
#include <vector>
#include "ballistica/ballistica.h"
#include "ode/ode.h"
namespace ballistica {
// Given geoms, creates/samples a height map on the fly
// which can be used for very fast AABB tests against the geometry.
class CollisionCache {
public:
CollisionCache();
~CollisionCache();
// If returns true, the provided AABB *may* intersect the geoms.
void SetGeoms(const std::vector<dGeomID>& geoms);
void Draw(FrameDef* f); // For debugging.
void CollideAgainstSpace(dSpaceID space, void* data, dNearCallback* callback);
void CollideAgainstGeom(dGeomID geom, void* data, dNearCallback* callback);
// Call this periodically (once per cycle or so) to slowly fill in
// the cache so there's less to do during spurts of activity;
void Precalc();
private:
void TestCell(size_t cell_index, int x, int z);
void Update();
uint32_t precalc_index_{};
std::vector<dGeomID> geoms_;
struct Cell {
float height_confirmed_empty_;
float height_confirmed_collide_;
};
std::vector<Cell> cells_;
std::vector<uint8_t> glow_;
bool dirty_;
dGeomID shadow_ray_;
dGeomID test_box_;
int grid_width_;
int grid_height_;
float cell_width_{};
float cell_height_{};
float x_min_;
float x_max_;
float y_min_;
float y_max_;
float z_min_;
float z_max_;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_COLLISION_CACHE_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_DYNAMICS_H_
#define BALLISTICA_DYNAMICS_DYNAMICS_H_
#include <map>
#include <memory>
#include <vector>
#include "ballistica/core/object.h"
#include "ode/ode.h"
namespace ballistica {
class Dynamics : public Object {
public:
explicit Dynamics(Scene* scene_in);
~Dynamics() override;
void Draw(FrameDef* frame_def); // Draw any debug stuff, etc.
auto ode_world() -> dWorldID { return ode_world_; }
auto getContactGroup() -> dJointGroupID { return ode_contact_group_; }
auto space() -> dSpaceID { return ode_space_; }
// Discontinues a collision. Used by parts when changing materials
// so that new collisions may enter effect.
void ResetCollision(int64_t node1, int part1, int64_t node2, int part2);
// Used by collision callbacks - internal.
auto active_collision() const -> Collision* { return active_collision_; }
// Used by collision callbacks - internal.
auto GetActiveCollideSrcNode() -> Node* {
assert(active_collision_);
return (collide_message_reverse_order_ ? active_collide_dst_node_
: active_collide_src_node_)
.get();
}
// Used by collision callbacks - internal.
auto GetActiveCollideDstNode() -> Node* {
assert(active_collision_);
return (collide_message_reverse_order_ ? active_collide_src_node_
: active_collide_dst_node_)
.get();
}
auto GetCollideMessageReverseOrder() const -> bool {
return collide_message_reverse_order_;
}
// Used by collide message handlers.
void set_collide_message_state(bool inCollideMessageIn,
bool target_other_in = false) {
in_collide_message_ = inCollideMessageIn;
collide_message_reverse_order_ = target_other_in;
}
auto in_collide_message() const -> bool { return in_collide_message_; }
void process();
void increment_skid_sound_count() { skid_sound_count_++; }
void decrement_skid_sound_count() { skid_sound_count_--; }
auto skid_sound_count() const -> int { return skid_sound_count_; }
void incrementRollSoundCount() { roll_sound_count_++; }
void decrement_roll_sound_count() { roll_sound_count_--; }
auto getRollSoundCount() const -> int { return roll_sound_count_; }
// We do some fancy collision testing stuff for trimeshes instead
// of going through regular ODE space collision testing.. so we have
// to keep track of these ourself.
void AddTrimesh(dGeomID g);
void RemoveTrimesh(dGeomID g);
auto collision_count() const -> int { return collision_count_; }
auto process_real_time() const -> millisecs_t { return real_time_; }
auto last_impact_sound_time() const -> millisecs_t {
return last_impact_sound_time_;
}
auto in_process() const -> bool { return in_process_; }
private:
auto AreColliding(const Part& p1, const Part& p2) -> bool;
class SrcNodeCollideMap;
class DstNodeCollideMap;
class SrcPartCollideMap;
class CollisionEvent;
class CollisionReset;
std::vector<CollisionReset> collision_resets_;
// Return a collision object between these two parts,
// creating a new one if need be.
auto GetCollision(Part* p1, Part* p2, MaterialContext** cc1,
MaterialContext** cc2) -> Collision*;
// Contains in-progress collisions for current nodes.
std::map<int64_t, SrcNodeCollideMap> node_collisions_;
std::vector<CollisionEvent> collision_events_;
void HandleDisconnect(
const std::map<int64_t,
ballistica::Dynamics::SrcNodeCollideMap>::iterator& i,
const std::map<int64_t,
ballistica::Dynamics::DstNodeCollideMap>::iterator& j,
const std::map<int, SrcPartCollideMap>::iterator& k,
const std::map<int, Object::Ref<Collision> >::iterator& l);
void ResetODE();
void ShutdownODE();
static void DoCollideCallback(void* data, dGeomID o1, dGeomID o2);
void CollideCallback(dGeomID o1, dGeomID o2);
void ProcessCollisions();
bool processing_collisions_ = false;
dWorldID ode_world_ = nullptr;
dJointGroupID ode_contact_group_ = nullptr;
dSpaceID ode_space_ = nullptr;
millisecs_t real_time_ = 0;
bool in_process_ = false;
std::vector<dGeomID> trimeshes_;
millisecs_t last_impact_sound_time_ = 0;
int skid_sound_count_ = 0;
int roll_sound_count_ = 0;
int collision_count_ = 0;
Scene* scene_;
bool in_collide_message_ = false;
bool collide_message_reverse_order_ = false;
Collision* active_collision_ = nullptr;
Object::WeakRef<Node> active_collide_src_node_;
Object::WeakRef<Node> active_collide_dst_node_;
std::unique_ptr<CollisionCache> collision_cache_;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_DYNAMICS_H_

View File

@ -0,0 +1,74 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/impact_sound_material_action.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/game/session/client_session.h"
#include "ballistica/generic/utils.h"
#include "ballistica/graphics/graphics_server.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
auto ImpactSoundMaterialAction::GetFlattenedSize() -> size_t {
// 1 byte for number of sounds plus 1 int per sound
return 1 + 4 * sounds.size() + 2 + 2;
}
void ImpactSoundMaterialAction::Flatten(char** buffer,
GameStream* output_stream) {
assert(sounds.size() < 100);
auto sound_count{static_cast<uint8_t>(sounds.size())};
Utils::EmbedInt8(buffer, sound_count);
for (int i = 0; i < sound_count; i++) {
Utils::EmbedInt32NBO(buffer,
static_cast_check_fit<int32_t>(
output_stream->GetSoundID(sounds[i].get())));
}
Utils::EmbedFloat16NBO(buffer, target_impulse_);
Utils::EmbedFloat16NBO(buffer, volume_);
}
void ImpactSoundMaterialAction::Restore(const char** buffer,
ClientSession* cs) {
int count{Utils::ExtractInt8(buffer)};
BA_PRECONDITION(count > 0 && count < 100);
sounds.clear();
for (int i = 0; i < count; i++) {
sounds.emplace_back(cs->GetSound(Utils::ExtractInt32NBO(buffer)));
}
target_impulse_ = Utils::ExtractFloat16NBO(buffer);
volume_ = Utils::ExtractFloat16NBO(buffer);
}
void ImpactSoundMaterialAction::Apply(MaterialContext* context,
const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
assert(context->dynamics.exists());
assert(context->dynamics->in_process());
// For now lets avoid this in low-quality graphics mode (should we make
// a low-quality sound mode?)
if (g_graphics_server
&& g_graphics_server->quality() < GraphicsQuality::kMedium) {
return;
}
// Let's only process impact-sounds a bit after the last one finished.
// (cut down on processing)
if (context->dynamics->process_real_time()
- context->dynamics->last_impact_sound_time()
> 100) {
assert(!sounds.empty());
context->impact_sounds.emplace_back(
context, sounds[rand() % sounds.size()].get(), // NOLINT
target_impulse_, volume_);
context->complex_sound = true;
}
}
} // namespace ballistica

View File

@ -0,0 +1,39 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_IMPACT_SOUND_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_IMPACT_SOUND_MATERIAL_ACTION_H_
#include <vector>
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
// A sound created based on collision forces parallel to the collision normal.
class ImpactSoundMaterialAction : public MaterialAction {
public:
ImpactSoundMaterialAction() = default;
ImpactSoundMaterialAction(const std::vector<Sound*>& sounds_in,
float target_impulse_in, float volume_in)
: sounds(PointersToRefs(sounds_in)),
target_impulse_(target_impulse_in),
volume_(volume_in) {}
std::vector<Object::Ref<Sound> > sounds;
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
auto GetType() const -> Type override { return Type::IMPACT_SOUND; }
auto GetFlattenedSize() -> size_t override;
void Flatten(char** buffer, GameStream* output_stream) override;
void Restore(const char** buffer, ClientSession* cs) override;
private:
float target_impulse_{};
float volume_{};
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_IMPACT_SOUND_MATERIAL_ACTION_H_

View File

@ -0,0 +1,81 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/material.h"
#include <string>
#include <utility>
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/dynamics/material/material_component.h"
#include "ballistica/dynamics/material/material_condition_node.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/python/python_sys.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
Material::Material(std::string name_in, Scene* scene)
: label_(std::move(name_in)), scene_(scene) {
// If we're being made in a scene with an output stream,
// write ourself to it.
assert(scene);
if (GameStream* os = scene->GetGameStream()) {
os->AddMaterial(this);
}
}
void Material::MarkDead() {
if (dead_) {
return;
}
components_.clear();
// If we're in a scene with an output-stream, inform them of our demise.
Scene* scene = scene_.get();
if (scene) {
if (GameStream* os = scene->GetGameStream()) {
os->RemoveMaterial(this);
}
}
dead_ = true;
}
auto Material::GetPyRef(bool new_ref) -> PyObject* {
if (!py_object_) {
throw Exception("This material is not associated with a python object");
}
if (new_ref) {
Py_INCREF(py_object_);
}
return py_object_;
}
Material::~Material() { MarkDead(); }
void Material::Apply(MaterialContext* s, const Part* src_part,
const Part* dst_part) {
// Apply all applicable components to the context.
for (auto& component : components_) {
if (component->eval_conditions(component->conditions, *this, src_part,
dst_part, *s)) {
component->Apply(s, src_part, dst_part);
}
}
}
void Material::AddComponent(const Object::Ref<MaterialComponent>& c) {
// If there's an output stream, push this to that first
if (GameStream* output_stream = scene()->GetGameStream()) {
output_stream->AddMaterialComponent(this, c.get());
}
components_.push_back(c);
}
void Material::DumpComponents(GameStream* out) {
for (auto& i : components_) {
assert(i.exists());
out->AddMaterialComponent(this, i.get());
}
}
} // namespace ballistica

View File

@ -0,0 +1,61 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_H_
#define BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_H_
#include <string>
#include <vector>
#include "ballistica/media/component/sound.h"
#include "ballistica/python/python_ref.h"
namespace ballistica {
/// A material defines actions that occur when a part collides with another part
/// (or separates from it after colliding). Materials can set up any number of
/// actions to occur dependent on what opposing materials are being hit, what
/// nodes are being hit, etc.
class Material : public Object {
public:
Material(std::string name, Scene* scene);
~Material() override;
/// Add a new component to the material.
/// Pass a component allocated via new.
void AddComponent(const Object::Ref<MaterialComponent>& c);
/// Apply the material to a context.
void Apply(MaterialContext* s, const Part* src_part, const Part* dst_part);
auto label() const -> const std::string& { return label_; }
auto hasPyObject() const -> bool { return (py_object_ != nullptr); }
auto NewPyRef() -> PyObject* { return GetPyRef(true); }
auto BorrowPyRef() -> PyObject* { return GetPyRef(false); }
void MarkDead();
auto scene() const -> Scene* { return scene_.get(); }
void DumpComponents(GameStream* out);
auto stream_id() const -> int64_t { return stream_id_; }
void set_stream_id(int64_t val) {
assert(stream_id_ == -1);
stream_id_ = val;
}
void clear_stream_id() {
assert(stream_id_ != -1);
stream_id_ = -1;
}
void set_py_object(PyObject* obj) { py_object_ = obj; }
auto py_object() const -> PyObject* { return py_object_; }
private:
bool dead_ = false;
int64_t stream_id_ = -1;
Object::WeakRef<Scene> scene_;
PyObject* py_object_ = nullptr;
auto GetPyRef(bool new_ref = true) -> PyObject*;
std::string label_;
std::vector<Object::Ref<MaterialComponent> > components_;
friend class ClientSession;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_H_

View File

@ -0,0 +1,51 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_ACTION_H_
#include "ballistica/core/object.h"
namespace ballistica {
class MaterialAction : public Object {
public:
enum class Type {
NODE_MESSAGE,
SCRIPT_COMMAND,
SCRIPT_CALL,
SOUND,
IMPACT_SOUND,
SKID_SOUND,
ROLL_SOUND,
NODE_MOD,
PART_MOD,
NODE_USER_MESSAGE
};
MaterialAction() = default;
virtual auto GetType() const -> Type = 0;
virtual void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) = 0;
virtual void Execute(Node* node1, Node* node2, Scene* scene) {}
virtual auto GetFlattenedSize() -> size_t { return 0; }
virtual void Flatten(char** buffer, GameStream* output_stream) {}
virtual void Restore(const char** buffer, ClientSession* cs) {}
auto IsNeededOnClient() -> bool {
switch (GetType()) {
case Type::NODE_MESSAGE:
case Type::SOUND:
case Type::IMPACT_SOUND:
case Type::SKID_SOUND:
case Type::ROLL_SOUND:
case Type::NODE_MOD:
case Type::PART_MOD:
return true;
default:
return false;
}
}
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_ACTION_H_

View File

@ -0,0 +1,227 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/material_component.h"
#include "ballistica/dynamics/material/impact_sound_material_action.h"
#include "ballistica/dynamics/material/material.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/dynamics/material/material_condition_node.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/dynamics/material/node_message_material_action.h"
#include "ballistica/dynamics/material/node_mod_material_action.h"
#include "ballistica/dynamics/material/part_mod_material_action.h"
#include "ballistica/dynamics/material/python_call_material_action.h"
#include "ballistica/dynamics/material/roll_sound_material_action.h"
#include "ballistica/dynamics/material/skid_sound_material_action.h"
#include "ballistica/dynamics/material/sound_material_action.h"
#include "ballistica/dynamics/part.h"
#include "ballistica/generic/utils.h"
#include "ballistica/scene/node/node.h"
namespace ballistica {
auto MaterialComponent::eval_conditions(
const Object::Ref<MaterialConditionNode>& condition, const Material& c,
const Part* part, const Part* opposing_part, const MaterialContext& s)
-> bool {
// If there's no condition, succeed.
if (!condition.exists()) {
return true;
}
// If we're a leaf node, evaluate.
if (condition->opmode == MaterialConditionNode::OpMode::LEAF_NODE) {
switch (condition->cond) {
case MaterialCondition::kTrue:
return true;
case MaterialCondition::kFalse:
return false;
case MaterialCondition::kDstIsMaterial:
return (
(opposing_part->ContainsMaterial(condition->val1_material.get())));
case MaterialCondition::kDstNotMaterial:
return (
!(opposing_part->ContainsMaterial(condition->val1_material.get())));
case MaterialCondition::kDstIsPart:
return ((opposing_part->id() == condition->val1));
case MaterialCondition::kDstNotPart:
return opposing_part->id() != condition->val1;
case MaterialCondition::kSrcDstSameMaterial:
return ((opposing_part->ContainsMaterial(&c)));
case MaterialCondition::kSrcDstDiffMaterial:
return (!(opposing_part->ContainsMaterial(&c)));
case MaterialCondition::kSrcDstSameNode:
return ((opposing_part->node() == part->node()));
case MaterialCondition::kSrcDstDiffNode:
return opposing_part->node() != part->node();
case MaterialCondition::kSrcYoungerThan:
return part->GetAge() < condition->val1;
case MaterialCondition::kSrcOlderThan:
return ((part->GetAge() >= condition->val1));
case MaterialCondition::kDstYoungerThan:
return opposing_part->GetAge() < condition->val1;
case MaterialCondition::kDstOlderThan:
return ((opposing_part->GetAge() >= condition->val1));
case MaterialCondition::kCollidingDstNode:
return (part->IsCollidingWith(opposing_part->node()->id()));
case MaterialCondition::kNotCollidingDstNode:
return (!(part->IsCollidingWith(opposing_part->node()->id())));
case MaterialCondition::kEvalColliding:
return s.collide && s.node_collide;
case MaterialCondition::kEvalNotColliding:
return (!s.collide || !s.node_collide);
default:
throw Exception();
}
} else {
// A trunk node; eval our left and right children and return
// the boolean operation between them.
assert(condition->left_child.exists());
assert(condition->right_child.exists());
bool left_result =
eval_conditions(condition->left_child, c, part, opposing_part, s);
// In some cases we don't even need to calc the right result.
switch (condition->opmode) {
case MaterialConditionNode::OpMode::AND_OPERATOR:
// AND can't succeed if left is false.
if (!left_result) return false;
break;
case MaterialConditionNode::OpMode::OR_OPERATOR:
// OR has succeeded if we've got a true.
if (left_result) return true;
break;
default:
break;
}
bool right_result =
eval_conditions(condition->right_child, c, part, opposing_part, s);
switch (condition->opmode) {
case MaterialConditionNode::OpMode::AND_OPERATOR:
return left_result && right_result;
case MaterialConditionNode::OpMode::OR_OPERATOR:
return left_result || right_result;
case MaterialConditionNode::OpMode::XOR_OPERATOR:
return ((left_result && !right_result)
|| (!left_result && right_result));
default:
throw Exception();
}
}
}
auto MaterialComponent::GetFlattenedSize() -> size_t {
size_t size{};
// Embed a byte telling whether we have conditions or not.
size += 1;
// Embed the size of the condition tree.
if (conditions.exists()) {
size += conditions->GetFlattenedSize();
}
// An int32 for the action count.
size += sizeof(uint32_t);
// Plus the total size of all actions.
for (auto& action : actions) {
if (action->IsNeededOnClient()) {
// 1 type byte plus the action's size.
size += 1 + action->GetFlattenedSize();
}
}
return size;
}
void MaterialComponent::Flatten(char** buffer, GameStream* output_stream) {
// Embed a byte telling whether we have conditions.
Utils::EmbedInt8(buffer, conditions.exists());
// If we have conditions, have the tree embed itself.
if (conditions.exists()) {
conditions->Flatten(buffer, output_stream);
}
// Embed our action count; we have to manually go through and count
// actions that we'll be sending.
int count = 0;
for (auto& action : actions) {
if ((*action).IsNeededOnClient()) {
assert((*action).GetType() != MaterialAction::Type::NODE_USER_MESSAGE);
count++;
}
}
Utils::EmbedInt32NBO(buffer, count);
// Embed our actions.
for (auto& action : actions) {
if ((*action).IsNeededOnClient()) {
Utils::EmbedInt8(buffer, static_cast<int8_t>((*action).GetType()));
(*action).Flatten(buffer, output_stream);
}
}
}
void MaterialComponent::Restore(const char** buffer, ClientSession* cs) {
// Pull the byte telling us if we have conditions.
bool haveConditions = Utils::ExtractInt8(buffer);
// If there's conditions, create a condition node and have it extract itself.
if (haveConditions) {
conditions = Object::New<MaterialConditionNode>();
conditions->Restore(buffer, cs);
}
// Pull our action count.
int action_count = Utils::ExtractInt32NBO(buffer);
// Restore all actions.
for (int i = 0; i < action_count; i++) {
// Pull the action type.
auto type = static_cast<MaterialAction::Type>(Utils::ExtractInt8(buffer));
Object::Ref<MaterialAction> action;
switch (type) {
case MaterialAction::Type::NODE_MESSAGE:
action = Object::New<NodeMessageMaterialAction>();
break;
case MaterialAction::Type::SOUND:
action = Object::New<SoundMaterialAction>();
break;
case MaterialAction::Type::IMPACT_SOUND:
action = Object::New<ImpactSoundMaterialAction>();
break;
case MaterialAction::Type::SKID_SOUND:
action = Object::New<SkidSoundMaterialAction>();
break;
case MaterialAction::Type::ROLL_SOUND:
action = Object::New<RollSoundMaterialAction>();
break;
case MaterialAction::Type::PART_MOD:
action = Object::New<PartModMaterialAction>();
break;
case MaterialAction::Type::NODE_MOD:
action = Object::New<NodeModMaterialAction>();
break;
default:
Log("Error: Invalid material action: '"
+ std::to_string(static_cast<int>(type)) + "'.");
throw Exception();
}
action->Restore(buffer, cs);
actions.push_back(action);
}
}
void MaterialComponent::Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part) {
assert(context && src_part && dst_part);
for (auto& action : actions) {
(*action).Apply(context, src_part, dst_part, action);
}
}
} // namespace ballistica

View File

@ -0,0 +1,44 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_COMPONENT_H_
#define BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_COMPONENT_H_
#include <utility>
#include <vector>
#include "ballistica/core/object.h"
namespace ballistica {
// A component of a material - comprises one or more conditions and actions.
class MaterialComponent : public Object {
public:
auto GetDefaultOwnerThread() const -> ThreadIdentifier override {
return ThreadIdentifier::kGame;
}
auto GetFlattenedSize() -> size_t;
void Flatten(char** buffer, GameStream* output_stream);
void Restore(const char** buffer, ClientSession* cs);
// Actions are stored as shared pointers so references
// to them can be stored with pending events
// in case the component is deleted before they are run.
std::vector<Object::Ref<MaterialAction> > actions;
Object::Ref<MaterialConditionNode> conditions;
auto eval_conditions(const Object::Ref<MaterialConditionNode>& condition,
const Material& c, const Part* part,
const Part* opposing_part, const MaterialContext& s)
-> bool;
// Apply the component to a context.
void Apply(MaterialContext* c, const Part* src_part, const Part* dst_part);
MaterialComponent() = default;
MaterialComponent(const Object::Ref<MaterialConditionNode>& conditions_in,
std::vector<Object::Ref<MaterialAction> > actions_in)
: conditions(conditions_in), actions(std::move(actions_in)) {}
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_COMPONENT_H_

View File

@ -0,0 +1,90 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/material_condition_node.h"
#include "ballistica/dynamics/material/material.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/game/session/client_session.h"
#include "ballistica/generic/utils.h"
namespace ballistica {
auto MaterialConditionNode::GetFlattenedSize() -> size_t {
// we need one byte for our opmode
// plus the condition byte and either 0, 1, or 2 values depending on our
// condition if we're a leaf node, otherwise add the size of our children
size_t size = 1;
if (opmode == OpMode::LEAF_NODE) {
size += 1 + sizeof(uint32_t) * GetValueCount();
} else {
size += (left_child->GetFlattenedSize() + right_child->GetFlattenedSize());
}
return size;
}
void MaterialConditionNode::Flatten(char** buffer, GameStream* output_stream) {
// Pack our opmode in. Or if we're a leaf note stick zero in.
Utils::EmbedInt8(buffer, static_cast<int8_t>(opmode));
if (opmode == OpMode::LEAF_NODE) {
Utils::EmbedInt8(buffer, static_cast<int8_t>(cond));
switch (GetValueCount()) {
case 0:
break;
case 1: {
// If this condition uses the material val1, embed its stream ID
if (cond == MaterialCondition::kDstIsMaterial
|| cond == MaterialCondition::kDstNotMaterial) {
Utils::EmbedInt32NBO(
buffer, static_cast_check_fit<int32_t>(
output_stream->GetMaterialID(val1_material.get())));
} else {
Utils::EmbedInt32NBO(buffer, val1);
}
break;
}
case 2:
Utils::EmbedInt32NBO(buffer, val1);
Utils::EmbedInt32NBO(buffer, val2);
break;
default:
throw Exception();
}
} else {
left_child->Flatten(buffer, output_stream);
right_child->Flatten(buffer, output_stream);
}
}
void MaterialConditionNode::Restore(const char** buffer, ClientSession* cs) {
opmode = static_cast<OpMode>(Utils::ExtractInt8(buffer));
if (opmode == OpMode::LEAF_NODE) {
cond = static_cast<MaterialCondition>(Utils::ExtractInt8(buffer));
int val_count = GetValueCount();
switch (val_count) {
case 0:
break;
case 1:
if (cond == MaterialCondition::kDstIsMaterial
|| cond == MaterialCondition::kDstNotMaterial) {
val1_material = cs->GetMaterial(Utils::ExtractInt32NBO(buffer));
} else {
val1 = Utils::ExtractInt32NBO(buffer);
}
break;
case 2:
val1 = Utils::ExtractInt32NBO(buffer);
val2 = Utils::ExtractInt32NBO(buffer);
break;
default:
throw Exception();
}
} else {
// not a leaf node - make ourself some children
left_child = Object::New<MaterialConditionNode>();
left_child->Restore(buffer, cs);
right_child = Object::New<MaterialConditionNode>();
right_child->Restore(buffer, cs);
}
}
} // namespace ballistica

View File

@ -0,0 +1,59 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_CONDITION_NODE_H_
#define BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_CONDITION_NODE_H_
#include "ballistica/ballistica.h"
#include "ballistica/core/object.h"
namespace ballistica {
class MaterialConditionNode : public Object {
public:
enum class OpMode { LEAF_NODE = 0, AND_OPERATOR, OR_OPERATOR, XOR_OPERATOR };
Object::Ref<MaterialConditionNode> left_child;
Object::Ref<MaterialConditionNode> right_child;
OpMode opmode{};
MaterialCondition cond{};
int val1{};
Object::Ref<Material> val1_material;
int val2{};
// Return the number of values used by this node
// assumes the node is a leaf node.
auto GetValueCount() -> int {
assert(opmode == OpMode::LEAF_NODE);
switch (cond) {
case MaterialCondition::kTrue:
case MaterialCondition::kFalse:
case MaterialCondition::kSrcDstSameMaterial:
case MaterialCondition::kSrcDstDiffMaterial:
case MaterialCondition::kSrcDstSameNode:
case MaterialCondition::kSrcDstDiffNode:
case MaterialCondition::kCollidingDstNode:
case MaterialCondition::kNotCollidingDstNode:
case MaterialCondition::kEvalColliding:
case MaterialCondition::kEvalNotColliding:
return 0;
case MaterialCondition::kDstIsMaterial:
case MaterialCondition::kDstNotMaterial:
case MaterialCondition::kSrcYoungerThan:
case MaterialCondition::kSrcOlderThan:
case MaterialCondition::kDstYoungerThan:
case MaterialCondition::kDstOlderThan:
return 1;
case MaterialCondition::kDstIsPart:
case MaterialCondition::kDstNotPart:
return 2;
default:
throw Exception();
}
}
auto GetFlattenedSize() -> size_t;
void Flatten(char** buffer, GameStream* output_stream);
void Restore(const char** buffer, ClientSession* cs);
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_CONDITION_NODE_H_

View File

@ -0,0 +1,93 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/audio/audio.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/media/component/sound.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
MaterialContext::MaterialContext(Scene* scene)
: dynamics(scene->dynamics()),
friction(1.0f),
stiffness(1.0f),
damping(1.0f),
bounce(0),
collide(true),
node_collide(true),
use_node_collide(true),
physical(true),
complex_sound(false) {}
MaterialContext::SkidSoundEntry::SkidSoundEntry(
const MaterialContext::SkidSoundEntry& other) {
*this = other;
assert(context);
#if BA_DEBUG_BUILD
assert(context->dynamics.exists());
#endif
assert(context->dynamics->in_process());
context->dynamics->increment_skid_sound_count();
}
MaterialContext::SkidSoundEntry::SkidSoundEntry(MaterialContext* context_in,
Sound* sound_in,
float target_impulse_in,
float volume_in)
: context(context_in),
sound(sound_in),
target_impulse(target_impulse_in),
volume(volume_in),
playing(false) {
assert(context);
assert(context->dynamics.exists());
assert(context->dynamics->in_process());
context->dynamics->increment_skid_sound_count();
}
MaterialContext::SkidSoundEntry::~SkidSoundEntry() {
assert(context);
assert(context->dynamics.exists());
context->dynamics->decrement_skid_sound_count();
if (playing) {
g_audio->PushSourceFadeOutCall(play_id, 200);
}
}
MaterialContext::RollSoundEntry::RollSoundEntry(MaterialContext* context_in,
Sound* sound_in,
float target_impulse_in,
float volume_in)
: context(context_in),
sound(sound_in),
target_impulse(target_impulse_in),
volume(volume_in),
playing(false) {
assert(context);
assert(context->dynamics.exists());
assert(context->dynamics->in_process());
context->dynamics->incrementRollSoundCount();
}
MaterialContext::RollSoundEntry::RollSoundEntry(
const MaterialContext::RollSoundEntry& other) {
*this = other;
assert(context);
assert(context->dynamics.exists());
assert(context->dynamics->in_process());
context->dynamics->incrementRollSoundCount();
}
MaterialContext::RollSoundEntry::~RollSoundEntry() {
assert(context);
assert(context->dynamics.exists());
context->dynamics->decrement_roll_sound_count();
if (playing) {
g_audio->PushSourceFadeOutCall(play_id, 200);
}
}
} // namespace ballistica

View File

@ -0,0 +1,90 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_CONTEXT_H_
#define BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_CONTEXT_H_
#include <vector>
#include "ballistica/core/object.h"
namespace ballistica {
// Contexts materials use when getting and setting collision data
class MaterialContext {
public:
BA_DEBUG_PTR(Dynamics) dynamics;
float friction{};
float stiffness{};
float damping{};
float bounce{};
bool collide{};
bool node_collide{};
bool use_node_collide{};
bool physical{};
// This should get set to true if
// anything is added to impact_sounds, skid_sounds, or roll_sounds.
// This way we know to calculate collision forces, relative velocities, etc.
bool complex_sound{};
std::vector<Object::Ref<MaterialAction> > connect_actions;
std::vector<Object::Ref<MaterialAction> > disconnect_actions;
struct SoundEntry {
Object::Ref<Sound> sound;
float volume;
SoundEntry(Sound* sound_in, float volume_in)
: sound(sound_in), volume(volume_in) {}
};
class ImpactSoundEntry {
public:
MaterialContext* context;
Object::Ref<Sound> sound;
float volume;
float target_impulse;
ImpactSoundEntry(MaterialContext* context, Sound* sound_in,
float target_impulse_in, float volume_in)
: context(context),
sound(sound_in),
target_impulse(target_impulse_in),
volume(volume_in) {}
};
class SkidSoundEntry {
public:
MaterialContext* context{};
Object::Ref<Sound> sound;
float volume{};
float target_impulse{};
// Used to keep track of the playing sound.
uint32_t play_id{};
bool playing{};
SkidSoundEntry(MaterialContext* context, Sound* sound_in,
float target_impulse_in, float volume_in);
~SkidSoundEntry();
SkidSoundEntry(const SkidSoundEntry& other);
};
class RollSoundEntry {
public:
MaterialContext* context{};
Object::Ref<Sound> sound;
float volume{};
float target_impulse{};
// Used to keep track of the playing sound.
uint32_t play_id{};
bool playing{};
RollSoundEntry(MaterialContext* context, Sound* sound_in,
float target_impulse_in, float volume_in);
RollSoundEntry(const RollSoundEntry& other);
~RollSoundEntry();
};
std::vector<SoundEntry> connect_sounds;
std::vector<ImpactSoundEntry> impact_sounds;
std::vector<SkidSoundEntry> skid_sounds;
std::vector<RollSoundEntry> roll_sounds;
explicit MaterialContext(Scene* scene_in);
private:
BA_DISALLOW_CLASS_COPIES(MaterialContext);
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_MATERIAL_CONTEXT_H_

View File

@ -0,0 +1,45 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/node_message_material_action.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/media/component/sound.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
NodeMessageMaterialAction::NodeMessageMaterialAction(bool target_other_in,
bool at_disconnect_in,
const char* data_in,
size_t length_in)
: target_other(target_other_in),
at_disconnect(at_disconnect_in),
data(data_in, length_in) {
assert(length_in > 0);
}
void NodeMessageMaterialAction::Apply(MaterialContext* context,
const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
if (at_disconnect) {
context->disconnect_actions.push_back(p);
} else {
context->connect_actions.push_back(p);
}
}
void NodeMessageMaterialAction::Execute(Node* node1, Node* node2,
Scene* scene) {
Node* node = target_other ? node2 : node1;
if (node) {
scene->dynamics()->set_collide_message_state(true, target_other);
assert(node);
assert(data.data());
node->DispatchNodeMessage(data.data());
scene->dynamics()->set_collide_message_state(false);
}
}
} // namespace ballistica

View File

@ -0,0 +1,42 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_NODE_MESSAGE_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_NODE_MESSAGE_MATERIAL_ACTION_H_
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/generic/buffer.h"
namespace ballistica {
// Regular message.
class NodeMessageMaterialAction : public MaterialAction {
public:
NodeMessageMaterialAction() = default;
NodeMessageMaterialAction(bool target_other_in, bool at_disconnect_in,
const char* data_in, size_t length_in);
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
void Execute(Node* node1, Node* node2, Scene* scene) override;
bool target_other{};
bool at_disconnect{};
Buffer<char> data;
auto GetType() const -> Type override { return Type::NODE_MESSAGE; }
auto GetFlattenedSize() -> size_t override {
// 1 byte for bools + data
return static_cast<int>(1 + data.GetFlattenedSize());
}
void Flatten(char** buffer, GameStream* output_stream) override {
Utils::EmbedBools(buffer, target_other, at_disconnect);
data.embed(buffer);
}
void Restore(const char** buffer, ClientSession* cs) override {
Utils::ExtractBools(buffer, &target_other, &at_disconnect);
data.Extract(buffer);
}
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_NODE_MESSAGE_MATERIAL_ACTION_H_

View File

@ -0,0 +1,41 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/node_mod_material_action.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/generic/utils.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
auto NodeModMaterialAction::GetType() const -> MaterialAction::Type {
return Type::NODE_MOD;
}
auto NodeModMaterialAction::GetFlattenedSize() -> size_t { return 1 + 4; }
void NodeModMaterialAction::Flatten(char** buffer, GameStream* output_stream) {
Utils::EmbedInt8(buffer, static_cast<int8_t>(attr));
Utils::EmbedFloat32(buffer, attr_val);
}
void NodeModMaterialAction::Restore(const char** buffer, ClientSession* cs) {
attr = static_cast<NodeCollideAttr>(Utils::ExtractInt8(buffer));
attr_val = Utils::ExtractFloat32(buffer);
}
void NodeModMaterialAction::Apply(MaterialContext* context,
const Part* src_part, const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
// Go ahead and make our modification to the context.
switch (attr) {
case NodeCollideAttr::kCollideNode:
context->node_collide = static_cast<bool>(attr_val);
break;
default:
throw Exception();
}
}
} // namespace ballistica

View File

@ -0,0 +1,28 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_NODE_MOD_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_NODE_MOD_MATERIAL_ACTION_H_
#include "ballistica/dynamics/material/material_action.h"
namespace ballistica {
class NodeModMaterialAction : public MaterialAction {
public:
NodeModMaterialAction() = default;
NodeModMaterialAction(NodeCollideAttr attr_in, float attr_val_in)
: attr(attr_in), attr_val(attr_val_in) {}
NodeCollideAttr attr{};
float attr_val{};
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
auto GetType() const -> Type override;
auto GetFlattenedSize() -> size_t override;
void Flatten(char** buffer, GameStream* output_stream) override;
void Restore(const char** buffer, ClientSession* cs) override;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_NODE_MOD_MATERIAL_ACTION_H_

View File

@ -0,0 +1,61 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/node_user_message_material_action.h"
#include "ballistica/core/context.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/media/component/sound.h"
#include "ballistica/scene/node/node.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
NodeUserMessageMaterialAction::NodeUserMessageMaterialAction(
bool target_other_in, bool at_disconnect_in, PyObject* user_message_obj_in)
: target_other(target_other_in), at_disconnect(at_disconnect_in) {
user_message_obj.Acquire(user_message_obj_in);
}
void NodeUserMessageMaterialAction::Apply(
MaterialContext* context, const Part* src_part, const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
if (at_disconnect) {
context->disconnect_actions.push_back(p);
} else {
context->connect_actions.push_back(p);
}
}
NodeUserMessageMaterialAction::~NodeUserMessageMaterialAction() = default;
void NodeUserMessageMaterialAction::Execute(Node* node1, Node* node2,
Scene* scene) {
// See who they want to send the message to.
Node* target_node = target_other ? node2 : node1;
if (!at_disconnect) {
// Only deliver 'connect' messages if both nodes still exist.
// This way handlers can avoid having to deal with that ultra-rare
// corner case.
if (!node1 || !node2) {
return;
}
} else {
// Deliver 'disconnect' messages if the target node still exists
// even if the opposing one doesn't. Nodes should always know when
// they stop colliding even if it was through death.
if (!target_node) {
return;
}
}
ScopedSetContext cp(target_node->context());
scene->dynamics()->set_collide_message_state(true, target_other);
target_node->DispatchUserMessage(user_message_obj.get(),
"Material User-Message dispatch");
scene->dynamics()->set_collide_message_state(false);
}
} // namespace ballistica

View File

@ -0,0 +1,30 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_NODE_USER_MESSAGE_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_NODE_USER_MESSAGE_MATERIAL_ACTION_H_
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/python/python_ref.h"
namespace ballistica {
// a user message - encapsulates a python object
class NodeUserMessageMaterialAction : public MaterialAction {
public:
NodeUserMessageMaterialAction(bool target_other, bool at_disconnect,
PyObject* user_message);
~NodeUserMessageMaterialAction() override;
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
void Execute(Node* node1, Node* node2, Scene* scene) override;
bool target_other;
bool at_disconnect;
PythonRef user_message_obj;
auto GetType() const -> Type override { return Type::NODE_USER_MESSAGE; }
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_NODE_USER_MESSAGE_MATERIAL_ACTION_H_

View File

@ -0,0 +1,58 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/part_mod_material_action.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/generic/utils.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
auto PartModMaterialAction::GetType() const -> MaterialAction::Type {
return Type::PART_MOD;
}
auto PartModMaterialAction::GetFlattenedSize() -> size_t { return 1 + 4; }
void PartModMaterialAction::Flatten(char** buffer, GameStream* output_stream) {
Utils::EmbedInt8(buffer, static_cast<int8_t>(attr));
Utils::EmbedFloat32(buffer, attr_val);
}
void PartModMaterialAction::Restore(const char** buffer, ClientSession* cs) {
attr = static_cast<PartCollideAttr>(Utils::ExtractInt8(buffer));
attr_val = Utils::ExtractFloat32(buffer);
}
void PartModMaterialAction::Apply(MaterialContext* context,
const Part* src_part, const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
// Go ahead and make our modification to the context.
switch (attr) {
case PartCollideAttr::kCollide:
context->collide = static_cast<bool>(attr_val);
break;
case PartCollideAttr::kUseNodeCollide:
context->use_node_collide = static_cast<bool>(attr_val);
break;
case PartCollideAttr::kPhysical:
context->physical = static_cast<bool>(attr_val);
break;
case PartCollideAttr::kFriction:
context->friction = attr_val;
break;
case PartCollideAttr::kStiffness:
context->stiffness = attr_val;
break;
case PartCollideAttr::kDamping:
context->damping = attr_val;
break;
case PartCollideAttr::kBounce:
context->bounce = attr_val;
break;
default:
throw Exception();
}
}
} // namespace ballistica

View File

@ -0,0 +1,28 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_PART_MOD_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_PART_MOD_MATERIAL_ACTION_H_
#include "ballistica/dynamics/material/material_action.h"
namespace ballistica {
class PartModMaterialAction : public MaterialAction {
public:
PartModMaterialAction() = default;
PartModMaterialAction(PartCollideAttr attr_in, float attr_val_in)
: attr(attr_in), attr_val(attr_val_in) {}
PartCollideAttr attr{};
float attr_val{};
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
auto GetType() const -> Type override;
auto GetFlattenedSize() -> size_t override;
void Flatten(char** buffer, GameStream* output_stream) override;
void Restore(const char** buffer, ClientSession* cs) override;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_PART_MOD_MATERIAL_ACTION_H_

View File

@ -0,0 +1,51 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/python_call_material_action.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/dynamics/material/sound_material_action.h"
#include "ballistica/media/component/sound.h"
#include "ballistica/python/python_context_call.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
PythonCallMaterialAction::PythonCallMaterialAction(bool at_disconnect_in,
PyObject* call_obj_in)
: at_disconnect(at_disconnect_in),
call(Object::New<PythonContextCall>(call_obj_in)) {}
void PythonCallMaterialAction::Apply(MaterialContext* context,
const Part* src_part, const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
if (at_disconnect) {
context->disconnect_actions.push_back(p);
} else {
context->connect_actions.push_back(p);
}
}
void PythonCallMaterialAction::Execute(Node* node1, Node* node2, Scene* scene) {
scene->dynamics()->set_collide_message_state(true, false);
// Only run connect commands if both nodes still exist.
// This way most collision commands can assume both
// members of the collision exist.
if (!at_disconnect) {
if (node1 && node2) {
call->Run();
}
} else {
// Its a disconnect. Run it if the src node still exists
// (nodes should know if they've disconnected from others even if
// it was through death)
if (node1) {
call->Run();
}
}
scene->dynamics()->set_collide_message_state(false);
}
} // namespace ballistica

View File

@ -0,0 +1,26 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_PYTHON_CALL_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_PYTHON_CALL_MATERIAL_ACTION_H_
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/python/python_context_call.h"
namespace ballistica {
class PythonCallMaterialAction : public MaterialAction {
public:
PythonCallMaterialAction(bool at_disconnect_in, PyObject* call_obj_in);
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
void Execute(Node* node1, Node* node2, Scene* scene) override;
bool at_disconnect;
Object::Ref<PythonContextCall> call;
auto GetType() const -> Type override { return Type::SCRIPT_CALL; }
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_PYTHON_CALL_MATERIAL_ACTION_H_

View File

@ -0,0 +1,53 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/roll_sound_material_action.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/game/session/client_session.h"
#include "ballistica/generic/utils.h"
#include "ballistica/graphics/graphics_server.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
auto RollSoundMaterialAction::GetFlattenedSize() -> size_t { return 4 + 2 + 2; }
void RollSoundMaterialAction::Flatten(char** buffer,
GameStream* output_stream) {
Utils::EmbedInt32NBO(buffer, static_cast_check_fit<int32_t>(
output_stream->GetSoundID(sound.get())));
Utils::EmbedFloat16NBO(buffer, target_impulse);
Utils::EmbedFloat16NBO(buffer, volume);
}
void RollSoundMaterialAction::Restore(const char** buffer, ClientSession* cs) {
sound = cs->GetSound(Utils::ExtractInt32NBO(buffer));
target_impulse = Utils::ExtractFloat16NBO(buffer);
volume = Utils::ExtractFloat16NBO(buffer);
}
void RollSoundMaterialAction::Apply(MaterialContext* context,
const Part* src_part, const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
assert(context->dynamics.exists());
assert(context->dynamics->in_process());
// For now lets avoid this in low-quality graphics mode
// (should we make a low-quality sound mode?)
if (g_graphics && g_graphics_server->quality() < GraphicsQuality::kMedium) {
return;
}
// Let's limit the amount of skid-sounds we spawn, otherwise we'll
// start using up all our sound resources on skids when things get messy
if (context->dynamics->getRollSoundCount() < 2) {
context->roll_sounds.emplace_back(context, sound.get(), target_impulse,
volume);
context->complex_sound = true;
}
}
} // namespace ballistica

View File

@ -0,0 +1,32 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_ROLL_SOUND_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_ROLL_SOUND_MATERIAL_ACTION_H_
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
namespace ballistica {
// Sound created based on velocity perpendicular to the collision normal.
class RollSoundMaterialAction : public MaterialAction {
public:
RollSoundMaterialAction() = default;
RollSoundMaterialAction(Sound* sound_in, float target_impulse_in,
float volume_in)
: sound(sound_in), target_impulse(target_impulse_in), volume(volume_in) {}
Object::Ref<Sound> sound;
float target_impulse{};
float volume{};
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
auto GetType() const -> Type override { return Type::ROLL_SOUND; }
auto GetFlattenedSize() -> size_t override;
void Flatten(char** buffer, GameStream* output_stream) override;
void Restore(const char** buffer, ClientSession* cs) override;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_ROLL_SOUND_MATERIAL_ACTION_H_

View File

@ -0,0 +1,54 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/skid_sound_material_action.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/game/session/client_session.h"
#include "ballistica/generic/utils.h"
#include "ballistica/graphics/graphics_server.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
auto SkidSoundMaterialAction::GetFlattenedSize() -> size_t { return 4 + 2 + 2; }
void SkidSoundMaterialAction::Flatten(char** buffer,
GameStream* output_stream) {
Utils::EmbedInt32NBO(buffer, static_cast_check_fit<int32_t>(
output_stream->GetSoundID(sound.get())));
Utils::EmbedFloat16NBO(buffer, target_impulse);
Utils::EmbedFloat16NBO(buffer, volume);
}
void SkidSoundMaterialAction::Restore(const char** buffer, ClientSession* cs) {
sound = cs->GetSound(Utils::ExtractInt32NBO(buffer));
target_impulse = Utils::ExtractFloat16NBO(buffer);
volume = Utils::ExtractFloat16NBO(buffer);
}
void SkidSoundMaterialAction::Apply(MaterialContext* context,
const Part* src_part, const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
assert(context->dynamics.exists());
assert(context->dynamics->in_process());
// For now lets avoid this in low-quality graphics mode
// (should we make a low-quality sound mode?).
if (g_graphics_server
&& g_graphics_server->quality() < GraphicsQuality::kMedium) {
return;
}
// Let's limit the amount of skid-sounds we spawn, otherwise we'll start
// using up all our sound resources on skids when things get messy.
if (context->dynamics->skid_sound_count() < 2) {
context->skid_sounds.emplace_back(context, sound.get(), target_impulse,
volume);
context->complex_sound = true;
}
}
} // namespace ballistica

View File

@ -0,0 +1,33 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_SKID_SOUND_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_SKID_SOUND_MATERIAL_ACTION_H_
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
// sound created based on collision forces perpendicular to the collision normal
class SkidSoundMaterialAction : public MaterialAction {
public:
SkidSoundMaterialAction() = default;
SkidSoundMaterialAction(Sound* sound_in, float target_impulse_in,
float volume_in)
: sound(sound_in), target_impulse(target_impulse_in), volume(volume_in) {}
Object::Ref<Sound> sound;
float target_impulse{};
float volume{};
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
auto GetType() const -> Type override { return Type::SKID_SOUND; }
auto GetFlattenedSize() -> size_t override;
void Flatten(char** buffer, GameStream* output_stream) override;
void Restore(const char** buffer, ClientSession* cs) override;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_SKID_SOUND_MATERIAL_ACTION_H_

View File

@ -0,0 +1,33 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/material/sound_material_action.h"
#include "ballistica/dynamics/material/material_context.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/game/session/client_session.h"
#include "ballistica/generic/utils.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
void SoundMaterialAction::Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) {
assert(context && src_part && dst_part);
context->connect_sounds.emplace_back(sound_.get(), volume_);
}
auto SoundMaterialAction::GetFlattenedSize() -> size_t { return 4 + 2; }
void SoundMaterialAction::Flatten(char** buffer, GameStream* output_stream) {
Utils::EmbedInt32NBO(buffer, static_cast_check_fit<int32_t>(
output_stream->GetSoundID(sound_.get())));
Utils::EmbedFloat16NBO(buffer, volume_);
}
void SoundMaterialAction::Restore(const char** buffer, ClientSession* cs) {
sound_ = cs->GetSound(Utils::ExtractInt32NBO(buffer));
volume_ = Utils::ExtractFloat16NBO(buffer);
}
} // namespace ballistica

View File

@ -0,0 +1,32 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_MATERIAL_SOUND_MATERIAL_ACTION_H_
#define BALLISTICA_DYNAMICS_MATERIAL_SOUND_MATERIAL_ACTION_H_
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
#include "ballistica/media/component/sound.h"
namespace ballistica {
class SoundMaterialAction : public MaterialAction {
public:
SoundMaterialAction() = default;
SoundMaterialAction(Sound* sound_in, float volume_in)
: sound_(sound_in), volume_(volume_in) {}
void Apply(MaterialContext* context, const Part* src_part,
const Part* dst_part,
const Object::Ref<MaterialAction>& p) override;
auto GetType() const -> Type override { return Type::SOUND; }
auto GetFlattenedSize() -> size_t override;
void Flatten(char** buffer, GameStream* output_stream) override;
void Restore(const char** buffer, ClientSession* cs) override;
private:
Object::Ref<Sound> sound_;
float volume_{};
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_MATERIAL_SOUND_MATERIAL_ACTION_H_

View File

@ -0,0 +1,131 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/part.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/material/material.h"
#include "ballistica/generic/buffer.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
Part::Part(Node* node, bool default_collide)
: our_id_(node->AddPart(this)),
default_collides_(default_collide),
node_(node) {
assert(node_.exists());
birth_time_ = node_->scene()->time();
dynamics_ = node_->scene()->dynamics();
}
Part::~Part() = default;
void Part::CheckBodies() {
for (auto&& i : rigid_bodies_) {
i->Check();
}
}
void Part::KillConstraints() {
for (auto&& i : rigid_bodies_) {
i->KillConstraints();
}
}
void Part::UpdateBirthTime() { birth_time_ = node_->scene()->time(); }
auto Part::GetMaterials() const -> std::vector<Material*> {
return RefsToPointers(materials_);
}
void Part::SetMaterials(const std::vector<Material*>& vals) {
assert(!Utils::HasNullMembers(vals));
// Hold strong refs to the materials passed.
materials_ = PointersToRefs(vals);
// Wake us up in case our new materials make us stop colliding or whatnot.
// (we may be asleep resting on something we suddenly no longer hit)
Wake();
// Reset all of our active collisions so new collisions will take effect
// with the new materials.
for (auto&& i : collisions_) {
dynamics_->ResetCollision(node()->id(), id(), i.node, i.part);
}
}
void Part::ApplyMaterials(MaterialContext* s, const Part* src_part,
const Part* dst_part) {
for (auto&& i : materials_) {
assert(i.exists());
i->Apply(s, src_part, dst_part);
}
}
auto Part::ContainsMaterial(const Material* m) const -> bool {
assert(m);
for (auto&& i : materials_) {
assert(i.exists());
if (m == i.get()) {
return true;
}
}
return false;
}
auto Part::IsCollidingWith(int64_t node, int part) const -> bool {
for (auto&& i : collisions_) {
if (i.node == node && i.part == part) return true;
}
return false;
}
auto Part::IsCollidingWith(int64_t node) const -> bool {
for (auto&& i : collisions_) {
if (i.node == node) {
return true;
}
}
return false;
}
void Part::SetCollidingWith(int64_t node_id, int part, bool colliding,
bool physical) {
if (colliding) {
// Add this to our list of collisions if its not on it.
for (auto&& i : collisions_) {
if (i.node == node_id && i.part == part) {
BA_PRECONDITION(node());
Log("Error: Got SetCollidingWith for part already colliding with.");
return;
}
}
collisions_.emplace_back(node_id, part);
} else {
// Make sure our bodies are awake - we may have been asleep
// resting on something that no longer exists.
if (physical) {
Wake();
}
// Remove the part from our colliding-with list.
for (auto i = collisions_.begin(); i != collisions_.end(); ++i) {
if (i->node == node_id && i->part == part) {
collisions_.erase(i);
return;
}
}
Log("Error: Got SetCollidingWith (separated) call for part we're "
"not colliding with.");
}
}
auto Part::GetAge() const -> millisecs_t {
assert(node_.exists());
assert(node_->scene()->time() >= birth_time_);
return node_->scene()->time() - birth_time_;
}
} // namespace ballistica

View File

@ -0,0 +1,139 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_PART_H_
#define BALLISTICA_DYNAMICS_PART_H_
#include <vector>
#include "ballistica/core/object.h"
#include "ballistica/dynamics/rigid_body.h"
namespace ballistica {
// A categorized "part" of a node which contains collision and other grouping
// information for a set of rigid bodies composing the part.
// Each rigid body is contained in exactly one part.
class Part : public Object {
public:
explicit Part(Node* node, bool default_collide = true);
~Part() override;
auto id() const -> int { return our_id_; }
// Used by RigidBodies when adding themselves to the part.
void AddBody(RigidBody* rigid_body_in) {
rigid_bodies_.push_back(rigid_body_in);
}
// Used by RigidBodies when removing themselves from the part.
void RemoveBody(RigidBody* rigid_body_in) {
for (auto i = rigid_bodies_.begin(); i != rigid_bodies_.end(); ++i) {
if (*i == rigid_body_in) {
rigid_bodies_.erase(i);
return;
}
}
throw Exception();
}
// Wakes up all rigid bodies in the part.
void Wake() {
for (auto&& i : rigid_bodies_) {
i->Wake();
}
}
auto node() const -> Node* {
assert(node_.exists());
return node_.get();
}
// Apply a set of materials to the part.
// Note than anytime a part's material set is changed,
// All collisions occurring between it and other parts are reset,
// so the old material set's separation commands will run and then
// the new material's collide commands will run (if there is still a
// collision)
void SetMaterials(const std::vector<Material*>& vals);
auto GetMaterials() const -> std::vector<Material*>;
// Apply this part's materials to a context.
void ApplyMaterials(MaterialContext* s, const Part* src_part,
const Part* dst_part);
// Returns true if the material is directly attached to the part
// note that having a material that calls the requested material does
// not count.
auto ContainsMaterial(const Material* m) const -> bool;
// Returns whether the part is currently colliding with the specified node.
auto IsCollidingWith(int64_t node) const -> bool;
// Returns whether the part is currently colliding with the specified
// node/part combo.
auto IsCollidingWith(int64_t node, int part) const -> bool;
// Used by g_game to inform us we're now colliding with another part
// if colliding is false, we've stopped colliding with this part.
void SetCollidingWith(int64_t node_id, int part, bool colliding,
bool physical);
// Kill constraints for all bodies in the part
// (useful when teleporting and things like that).
void KillConstraints();
auto default_collides() const -> bool { return default_collides_; }
auto GetAge() const -> millisecs_t;
// Birthtime can be used to prevent spawning or teleporting parts from
// colliding with things they are overlapping.
// Any part with teleporting parts should use this to
// reset their birth times. Nodes have a function to do so for all their
// contained parts as well.
void UpdateBirthTime();
auto last_impact_sound_time() const -> millisecs_t {
return last_impact_sound_time_;
}
auto last_skid_sound_time() const -> millisecs_t {
return last_skid_sound_time_;
}
auto last_roll_sound_time() const -> millisecs_t {
return last_roll_sound_time_;
}
void set_last_impact_sound_time(millisecs_t t) {
last_impact_sound_time_ = t;
}
void set_last_skid_sound_time(millisecs_t t) { last_skid_sound_time_ = t; }
void set_last_roll_sound_time(millisecs_t t) { last_roll_sound_time_ = t; }
auto rigid_bodies() const -> const std::vector<RigidBody*>& {
return rigid_bodies_;
}
// Debugging: check for NaNs and whatnot.
void CheckBodies();
private:
Dynamics* dynamics_;
class Collision {
public:
int node;
int part;
Collision(int node_in, int part_in) : node(node_in), part(part_in) {}
};
// Collisions currently affecting us stored for quick access.
std::vector<Collision> collisions_;
bool default_collides_;
millisecs_t birth_time_;
int our_id_;
Object::WeakRef<Node> node_;
std::vector<Object::Ref<Material> > materials_;
std::vector<RigidBody*> rigid_bodies_;
// Last time this part played a collide sound (used by the audio system).
millisecs_t last_impact_sound_time_ = 0;
millisecs_t last_skid_sound_time_ = 0;
millisecs_t last_roll_sound_time_ = 0;
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_PART_H_

View File

@ -0,0 +1,693 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/dynamics/rigid_body.h"
#include <cmath>
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/dynamics/part.h"
#include "ballistica/generic/utils.h"
#include "ballistica/graphics/renderer.h"
#include "ballistica/media/component/collide_model.h"
#include "ballistica/scene/scene.h"
#include "ode/ode_collision_util.h"
namespace ballistica {
// whether to send our net states as half float format
#define USE_HALF_FLOATS 1
#define EMBED_POS_FLOAT Utils::EmbedFloat32
#define EXTRACT_POS_FLOAT Utils::ExtractFloat32
#define POS_FLOAT_DATA_SIZE 4
#if USE_HALF_FLOATS
#define FLOAT_DATA_SIZE 2
#define EMBED_FLOAT Utils::EmbedFloat16NBO
#define EXTRACT_FLOAT Utils::ExtractFloat16NBO
#else
#define FLOAT_DATA_SIZE 4
#define EMBED_FLOAT Utils::EmbedFloat32
#define EXTRACT_FLOAT Utils::ExtractFloat32
#endif
#define ABSOLUTE_EPSILON 0.001f
RigidBody::RigidBody(int id_in, Part* part_in, Type type_in, Shape shape_in,
uint32_t collide_type_in, uint32_t collide_mask_in,
CollideModel* collide_model_in, uint32_t flags)
: type_(type_in),
id_(id_in),
creation_time_(part_in->node()->scene()->time()),
shape_(shape_in),
part_(part_in),
collide_model_(collide_model_in),
collide_type_(collide_type_in),
collide_mask_(collide_mask_in),
flags_(flags) {
blend_time_ = creation_time_;
#if BA_DEBUG_BUILD
for (int i = 0; i < 3; i++) {
prev_pos_[i] = prev_vel_[i] = prev_a_vel_[i] = 0.0f;
}
#endif // BA_DEBUG_BUILD
assert(part_.exists());
birth_time_ = part_->node()->scene()->stepnum();
dynamics_ = part_->node()->scene()->dynamics();
// Add ourself to the part.
part_->AddBody(this);
// Create the geom(s).
switch (shape_) {
case Shape::kSphere: {
dimensions_[0] = dimensions_[1] = dimensions_[2] = 0.3f;
geoms_.resize(1);
geoms_[0] = dCreateSphere(dynamics_->space(), dimensions_[0]);
break;
}
case Shape::kBox: {
dimensions_[0] = dimensions_[1] = dimensions_[2] = 0.6f;
geoms_.resize(1);
geoms_[0] = dCreateBox(dynamics_->space(), dimensions_[0], dimensions_[1],
dimensions_[2]);
break;
}
case Shape::kCapsule: {
dimensions_[0] = dimensions_[1] = 0.3f;
geoms_.resize(1);
geoms_[0] =
dCreateCCylinder(dynamics_->space(), dimensions_[0], dimensions_[1]);
break;
}
case Shape::kCylinder: {
int sphere_count = 8;
float inc = 360.0f / static_cast<float>(sphere_count);
// Transform geom and sphere.
geoms_.resize(static_cast<uint32_t>(2 * sphere_count + 1));
dimensions_[0] = dimensions_[1] = 0.3f;
float sub_rad = dimensions_[1] * 0.5f;
float offset = dimensions_[0] - sub_rad;
for (int i = 0; i < sphere_count; i++) {
Vector3f p =
Matrix44fRotate(Vector3f(0, 1, 0), static_cast<float>(i) * inc)
* Vector3f(offset, 0, 0);
geoms_[i * 2] = dCreateGeomTransform(dynamics_->space());
geoms_[i * 2 + 1] = dCreateSphere(nullptr, sub_rad);
dGeomTransformSetGeom(geoms_[i * 2], geoms_[i * 2 + 1]);
dGeomSetPosition(geoms_[i * 2 + 1], p.v[0], p.v[1], p.v[2]);
}
// One last center sphere to keep stuff from getting stuck in our middle.
geoms_[geoms_.size() - 1] = dCreateSphere(dynamics_->space(), sub_rad);
break;
}
case Shape::kTrimesh: {
// NOTE - we don't add trimeshes do the collision space - we handle them
// specially..
dimensions_[0] = dimensions_[1] = dimensions_[2] = 0.6f;
assert(collide_model_.exists());
collide_model_->collide_model_data()->Load();
dGeomID g = dCreateTriMesh(
nullptr, collide_model_->collide_model_data()->GetMeshData(), nullptr,
nullptr, nullptr);
geoms_.push_back(g);
dynamics_->AddTrimesh(g);
break;
}
default:
throw Exception();
}
for (auto&& i : geoms_) {
dGeomSetData(i, this);
}
if (type_ == Type::kBody) {
assert(body_ == nullptr);
body_ = dBodyCreate(dynamics_->ode_world());
// For cylinders we only set the transform geoms, not the spheres.
if (shape_ == Shape::kCylinder) {
for (size_t i = 0; i < geoms_.size(); i += 2) {
dGeomSetBody(geoms_[i], body_);
}
// Our center sphere.
dGeomSetBody(geoms_[geoms_.size() - 1], body_);
} else {
dGeomSetBody(geoms_[0], body_);
}
}
SetDimensions(dimensions_[0], dimensions_[1], dimensions_[2]);
}
void RigidBody::Check() {
if (type_ == Type::kBody) {
const dReal* p = dBodyGetPosition(body_);
const dReal* q = dBodyGetQuaternion(body_);
const dReal* lv = dBodyGetLinearVel(body_);
const dReal* av = dBodyGetAngularVel(body_);
bool err = false;
for (int i = 0; i < 3; i++) {
if (std::isnan(p[i]) || std::isnan(q[i]) || std::isnan(lv[i])
|| std::isnan(av[i])) {
err = true;
break;
}
if (std::abs(p[i]) > 9999) err = true;
if (std::abs(lv[i]) > 99999) err = true;
if (std::abs(av[i]) > 9999) err = true;
}
if (std::isnan(q[3])) err = true;
if (err) {
Log("Error: Got error in rbd values!");
}
#if BA_DEBUG_BUILD
for (int i = 0; i < 3; i++) {
prev_pos_[i] = p[i];
prev_vel_[i] = lv[i];
prev_a_vel_[i] = av[i];
}
#endif
}
}
RigidBody::~RigidBody() {
if (shape_ == Shape::kTrimesh) {
assert(geoms_.size() == 1);
dynamics_->RemoveTrimesh(geoms_[0]);
}
// if we have any joints attached, kill them
KillConstraints();
// remove ourself from our parent part if we have one
if (part_.exists()) {
part_->RemoveBody(this);
}
if (type_ == Type::kBody) {
assert(body_);
dBodyDestroy(body_);
body_ = nullptr;
}
assert(!geoms_.empty());
for (auto&& i : geoms_) {
dGeomDestroy(i);
}
}
void RigidBody::KillConstraints() {
while (joints_.begin() != joints_.end()) {
(**joints_.begin()).Kill();
}
}
auto RigidBody::GetEmbeddedSizeFull() -> int {
assert(type_ == Type::kBody);
const dReal* lv = dBodyGetLinearVel(body_);
const dReal* av = dBodyGetAngularVel(body_);
// always have 3 position, 4 quaternion, and 1 flag
int full_size = 3 * POS_FLOAT_DATA_SIZE + FLOAT_DATA_SIZE * 4 + 1;
// we only send velocity values that are non-zero - calculate how many of
// them we have
for (int i = 0; i < 3; i++) {
full_size += FLOAT_DATA_SIZE * (std::abs(lv[i] - 0) > ABSOLUTE_EPSILON);
full_size += FLOAT_DATA_SIZE * (std::abs(av[i] - 0) > ABSOLUTE_EPSILON);
}
return full_size;
}
// store a body to a buffer
// FIXME - theoretically we should embed birth-time
// as this can affect collisions with this object
void RigidBody::EmbedFull(char** buffer) {
assert(type_ == Type::kBody);
const dReal* p = dBodyGetPosition(body_);
const dReal* q = dBodyGetQuaternion(body_);
const dReal* lv = dBodyGetLinearVel(body_);
const dReal* av = dBodyGetAngularVel(body_);
bool enabled = static_cast<bool>(dBodyIsEnabled(body_));
bool lv_changed[3];
bool av_changed[3];
// only send velocities that are non-zero.
// we always send position/rotation since that's not likely to be zero
for (int i = 0; i < 3; i++) {
lv_changed[i] = (std::abs(lv[i] - 0) > ABSOLUTE_EPSILON);
av_changed[i] = (std::abs(av[i] - 0) > ABSOLUTE_EPSILON);
}
// embed a byte containing our enabled state as well as what velocities need
// to be sent
Utils::EmbedBools(buffer, lv_changed[0], lv_changed[1], lv_changed[2],
av_changed[0], av_changed[1], av_changed[2], enabled);
EMBED_POS_FLOAT(buffer, p[0]);
EMBED_POS_FLOAT(buffer, p[1]);
EMBED_POS_FLOAT(buffer, p[2]);
EMBED_FLOAT(buffer, q[0]);
EMBED_FLOAT(buffer, q[1]);
EMBED_FLOAT(buffer, q[2]);
EMBED_FLOAT(buffer, q[3]);
for (int i = 0; i < 3; i++) {
if (lv_changed[i]) {
EMBED_FLOAT(buffer, lv[i]);
}
if (av_changed[i]) {
EMBED_FLOAT(buffer, av[i]);
}
}
}
// Position a body from buffer data.
void RigidBody::ExtractFull(const char** buffer) {
assert(type_ == Type::kBody);
dReal p[3], lv[3], av[3];
dQuaternion q;
bool lv_changed[3];
bool av_changed[3];
bool enabled;
// Extract our byte telling which velocities are contained here as well as our
// enable state.
Utils::ExtractBools(buffer, &lv_changed[0], &lv_changed[1], &lv_changed[2],
&av_changed[0], &av_changed[1], &av_changed[2], &enabled);
p[0] = EXTRACT_POS_FLOAT(buffer);
p[1] = EXTRACT_POS_FLOAT(buffer);
p[2] = EXTRACT_POS_FLOAT(buffer);
q[0] = EXTRACT_FLOAT(buffer);
q[1] = EXTRACT_FLOAT(buffer);
q[2] = EXTRACT_FLOAT(buffer);
q[3] = EXTRACT_FLOAT(buffer);
for (int i = 0; i < 3; i++) {
if (lv_changed[i]) {
lv[i] = EXTRACT_FLOAT(buffer);
} else {
lv[i] = 0;
}
if (av_changed[i]) {
av[i] = EXTRACT_FLOAT(buffer);
} else {
av[i] = 0;
}
}
dBodySetPosition(body_, p[0], p[1], p[2]);
dBodySetQuaternion(body_, q);
dBodySetLinearVel(body_, lv[0], lv[1], lv[2]);
dBodySetAngularVel(body_, av[0], av[1], av[2]);
if (enabled) {
dBodyEnable(body_);
} else {
dBodyDisable(body_);
}
}
void RigidBody::Draw(RenderPass* pass, bool shaded) {
assert(pass);
RenderPass::Type pass_type = pass->type();
// only passes we draw in are light_shadow and beauty
if (pass_type != RenderPass::Type::kLightShadowPass
&& pass_type != RenderPass::Type::kBeautyPass) {
return;
}
// assume trimeshes are landscapes and shouldn't be in shadow passes..
if (shape_ == Shape::kTrimesh
&& (pass_type != RenderPass::Type::kBeautyPass)) {
return;
}
}
void RigidBody::AddCallback(CollideCallbackFunc callbackIn, void* data_in) {
CollideCallback c{};
c.callback = callbackIn;
c.data = data_in;
collide_callbacks_.push_back(c);
}
auto RigidBody::CallCollideCallbacks(dContact* contacts, int count,
RigidBody* opposingbody) -> bool {
for (auto&& i : collide_callbacks_) {
if (!i.callback(contacts, count, this, opposingbody, i.data)) {
return false;
}
}
return true;
}
void RigidBody::SetDimensions(float d1, float d2, float d3, float m1, float m2,
float m3, float density_mult) {
dimensions_[0] = d1;
dimensions_[1] = d2;
dimensions_[2] = d3;
if (m1 == 0.0f) m1 = d1;
if (m2 == 0.0f) m2 = d2;
if (m3 == 0.0f) m3 = d3;
float density = 5.0f * density_mult;
switch (shape_) {
case Shape::kSphere:
dGeomSphereSetRadius(geoms_[0], dimensions_[0]);
break;
case Shape::kBox:
dGeomBoxSetLengths(geoms_[0], dimensions_[0], dimensions_[1],
dimensions_[2]);
break;
case Shape::kCapsule:
dGeomCCylinderSetParams(geoms_[0], dimensions_[0], dimensions_[1]);
break;
case Shape::kCylinder: {
int sphere_count = static_cast<int>(geoms_.size() / 2);
float inc = 360.0f / static_cast<float>(sphere_count);
float sub_rad = dimensions_[1] * 0.5f;
float offset = dimensions_[0] - sub_rad;
for (int i = 0; i < sphere_count; i++) {
Vector3f p =
Matrix44fRotate(Vector3f(0, 0, 1), static_cast<float>(i) * inc)
* Vector3f(offset, 0, 0);
dGeomSphereSetRadius(geoms_[i * 2 + 1], sub_rad);
dGeomSetPosition(geoms_[i * 2 + 1], p.v[0], p.v[1], p.v[2]);
}
// Resize our center sphere.
dGeomSphereSetRadius(geoms_[geoms_.size() - 1], sub_rad);
}
// A cylinder is really just a bunch of spheres - we just need to set the
// length of their offsets.
// dGeomBoxSetLengths(geoms[0],dimensions[0],dimensions[0],dimensions[1]);
break;
case Shape::kTrimesh:
break;
default:
throw Exception();
}
// Create the body and set mass properties.
if (type_ == Type::kBody) {
dMass m;
switch (shape_) {
case Shape::kSphere:
dMassSetSphere(&m, density, m1);
break;
case Shape::kBox:
dMassSetBox(&m, density, m1, m2, m3);
break;
case Shape::kCapsule:
dMassSetCappedCylinder(&m, density, 3, m1, m2);
break;
case Shape::kCylinder:
dMassSetCylinder(&m, density, 3, m1, m2);
break;
case Shape::kTrimesh: // NOLINT(bugprone-branch-clone)
// Trimesh bodies not supported yet.
throw Exception();
default:
throw Exception();
}
// Need to handle groups here.
assert(geoms_.size() == 1 || shape_ == Shape::kCylinder);
dBodySetMass(body_, &m);
}
}
auto RigidBody::ApplyImpulse(float px, float py, float pz, float vx, float vy,
float vz, float fdirx, float fdiry, float fdirz,
float mag, float v_mag, float radius,
bool calc_only) -> float {
assert(body_);
float total_mag = 0.0f;
dMass mass;
dBodyGetMass(body_, &mass);
bool horizontal_only = false;
// FIXME - some hard-coded tweaks for the hockey-puck
if (shape_ == Shape::kCylinder) {
py -= 0.3f;
if (v_mag > 0.0f) {
v_mag *= 0.06f; // punches
} else {
mag *= 3.0f; // amp up explosions
}
horizontal_only = true;
}
if (radius <= 0.0f) {
// Damage based on velocity difference.. lets just plug in our
// center-of-mass velocity (otherwise we might get crazy large velocity
// diffs due to spinning).
// Ok for now we're not taking our velocity into account.
dVector3 our_velocity = {0, 0, 0};
dVector3 v_diff = {vx - our_velocity[0], vy - our_velocity[1],
vz - our_velocity[2]};
dVector3 f = {fdirx, fdiry, fdirz};
// normalize..
float fDirLen = sqrtf(fdirx * fdirx + fdiry * fdiry + fdirz * fdirz);
if (fDirLen > 0.0f) {
f[0] /= fDirLen;
f[1] /= fDirLen;
f[2] /= fDirLen;
} else {
f[0] = 1.0f; // just use (1,0,0)
}
// Lets only take large velocity diffs into account.
// float vLen = std::max(0.0f,dVector3Length(v_diff)-2.0f);
float vLen = dVector3Length(v_diff);
total_mag = mag + vLen * v_mag;
f[0] *= total_mag;
f[1] *= total_mag;
f[2] *= total_mag;
// Exaggerate the force we apply in y (but don't count it towards damage).
f[1] *= 2.0f;
// General scale up.
f[0] *= 1.8f;
f[1] *= 1.8f;
f[2] *= 1.8f;
if (horizontal_only) {
f[1] = 0.0f;
py = dBodyGetPosition(body_)[1];
}
if (!calc_only) {
dBodyEnable(body_);
dBodyAddForceAtPos(body_, f[0], f[1], f[2], px, py, pz);
}
} else {
// With radius.
Vector3f us(dBodyGetPosition(body_));
Vector3f them(px, py, pz);
if (them == us) {
them = us + Vector3f(0.0f, 0.001f, 0.0f);
}
Vector3f diff = them - us;
float len = (them - us).Length();
if (len == 0.0f) {
len = 0.0001f;
}
if (len < radius) {
float amt = 1.0f - (len / radius);
if (v_mag > 0.0f) {
throw Exception("FIXME - handle vmag for radius>0 case");
}
// Factor in our mass so a given impulse affects various sized things
// equally.
float this_mag = (mag * amt) * mass.mass;
// amt *= amt; // squared falloff..
// amt = pow(amt, 1.5f); // biased falloff
total_mag += this_mag;
Vector3f f = diff * (-this_mag / len);
// Randomize applied force a bit to keep things from looking too clean and
// simple.
const dReal* pos = dBodyGetPosition(body_);
dReal apply_pos[3] = {pos[0] + 0.6f * (RandomFloat() - 0.5f),
pos[1] + 0.6f * (RandomFloat() - 0.5f),
pos[2] + 0.6f * (RandomFloat() - 0.5f)};
if (horizontal_only) {
f.y = 0.0f;
apply_pos[1] = us.y;
}
// Exaggerate up/down component.
f.x *= 0.5f;
if (f.y > 0.0f) {
f.y *= 2.0f;
}
f.z *= 0.5f;
if (!calc_only) {
dBodyEnable(body_);
dBodyAddForceAtPos(body_, f.x, f.y, f.z, apply_pos[0], apply_pos[1],
apply_pos[2]);
}
}
}
return total_mag;
}
void RigidBody::ApplyGlobalImpulse(float px, float py, float pz, float fx,
float fy, float fz) {
if (type_ != Type::kBody) {
return;
}
dBodyEnable(body_);
dBodyAddForceAtPos(body_, fx / kGameStepSeconds, fy / kGameStepSeconds,
fz / kGameStepSeconds, px, py, pz);
}
RigidBody::Joint::Joint() = default;
void RigidBody::Joint::SetJoint(dxJointFixed* id_in, Scene* scene) {
Kill();
creation_time_ = scene->time();
id_ = id_in;
}
RigidBody::Joint::~Joint() { Kill(); }
void RigidBody::Joint::AttachToBodies(RigidBody* b1_in, RigidBody* b2_in) {
assert(id_);
b1_ = b1_in;
b2_ = b2_in;
dBodyID b_id_1 = nullptr;
dBodyID b_id_2 = nullptr;
if (b1_) {
b1_->Wake();
b1_->AddJoint(this);
b_id_1 = b1_->body();
}
if (b2_) {
b2_->Wake();
b2_->AddJoint(this);
b_id_2 = b2_->body();
}
dJointAttach(id_, b_id_1, b_id_2);
}
void RigidBody::Joint::Kill() {
if (id_) {
if (b1_) {
b1_->RemoveJoint(this);
// Also wake the body (this joint could be suspending it motionless).
assert(b1_->body());
dBodyEnable(b1_->body());
}
if (b2_) {
b2_->RemoveJoint(this);
// Also wake the body (this joint could be suspending it motionless).
assert(b2_->body());
dBodyEnable(b2_->body());
}
dJointDestroy(id_);
id_ = nullptr;
b1_ = b2_ = nullptr;
}
}
auto RigidBody::GetTransform() -> Matrix44f {
Matrix44f matrix{kMatrix44fIdentity};
const dReal* pos_in;
const dReal* r_in;
if (type() == RigidBody::Type::kBody) {
pos_in = dBodyGetPosition(body());
r_in = dBodyGetRotation(body());
} else {
pos_in = dGeomGetPosition(geom());
r_in = dGeomGetRotation(geom());
}
float pos[3];
float r[12];
for (int x = 0; x < 3; x++) {
pos[x] = pos_in[x];
}
pos[0] += blend_offset().x;
pos[1] += blend_offset().y;
pos[2] += blend_offset().z;
for (int x = 0; x < 12; x++) {
r[x] = r_in[x];
}
matrix.m[0] = r[0];
matrix.m[1] = r[4];
matrix.m[2] = r[8];
matrix.m[3] = 0;
matrix.m[4] = r[1];
matrix.m[5] = r[5];
matrix.m[6] = r[9];
matrix.m[7] = 0;
matrix.m[8] = r[2];
matrix.m[9] = r[6];
matrix.m[10] = r[10];
matrix.m[11] = 0;
matrix.m[12] = pos[0];
matrix.m[13] = pos[1];
matrix.m[14] = pos[2];
matrix.m[15] = 1;
return matrix;
}
void RigidBody::AddBlendOffset(float x, float y, float z) {
// blend_offset_.x += x;
// blend_offset_.y += y;
// blend_offset_.z += z;
}
void RigidBody::UpdateBlending() {
// FIXME - this seems broken. We never update blend_time_ currently
// and its also set to time whereas we're comparing it with steps.
// Should revisit.
// millisecs_t diff = part()->node()->scene()->stepnum() - blend_time_;
// diff = std::min(millisecs_t{10}, diff);
// for (millisecs_t i = 0; i < diff; i++) {
// blend_offset_.x *= 0.995f;
// blend_offset_.y *= 0.995f;
// blend_offset_.z *= 0.995f;
// }
}
} // namespace ballistica

View File

@ -0,0 +1,215 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_DYNAMICS_RIGID_BODY_H_
#define BALLISTICA_DYNAMICS_RIGID_BODY_H_
#include <list>
#include <vector>
#include "ballistica/core/object.h"
#include "ballistica/math/matrix44f.h"
#include "ode/ode.h"
#include "ode/ode_joint.h"
namespace ballistica {
// Wrapper for ode rigid bodies which implements collision tracking,
// flattening/restoring, and other extras.
class RigidBody : public Object {
public:
// Function type for low level collision callbacks.
// These callbacks are called just before collision constraints
// are being created between rigid bodies. These callback
// should be used only for contact adjustment - things like
// changing friction depending on what part of the body was hit, etc.
// Never use these callbacks to run script command or anything high-level.
// Return false to cancel all constraint creation.
typedef bool (*CollideCallbackFunc)(dContact* contacts, int count,
RigidBody* collide_body,
RigidBody* opposingbody,
void* custom_data);
enum class Type {
// Collidable but not dynamically affected object.
// Used to generate collisions.
kGeomOnly,
// Collidable as well as dynamically affected object.
kBody
};
// Used to determine what kind of surface a body has and what surfaces it will
// collide against a body defines its own collide type(s) and its mask for
// what it will collide against collisions will only occur if each body's
// collide mask includes the opposite body's type(s).
enum CollideType {
kCollideNone = 0,
// Static background objects such as landscapes
// These never move and generally never need to test for collisions against
// other landscapes
kCollideBackground = 0x01u,
// Regions - these generally only test for collisions with active bodies
kCollideRegion = 0x01u << 2u,
// Active bodies - these generally collide against everything
kCollideActive = 0x01u << 3u,
// encapsulates all collide types
kCollideAll = kCollideBackground | kCollideRegion | kCollideActive
};
// Different kinds of geometry a body can be.
enum class Shape {
// Simple sphere shape
kSphere,
// Simple cube shape
kBox,
// Capsule
kCapsule,
// cylinder made from 4 cubes (8 sides)
kCylinder,
// Trimesh
kTrimesh
};
enum Flag {
// The body is a 'bumper' - something that under-control character bodies
// might want to collide with but most other stuff won't want to.
kIsBumper = 1u << 0u,
kIsRoller = 1u << 1u,
kIsTerrain = 1u << 2u
};
// these are needed for full states
auto GetEmbeddedSizeFull() -> int;
void ExtractFull(const char** buffer);
void EmbedFull(char** buffer);
RigidBody(int id_in, Part* part_in, Type type_in, Shape shape_in,
uint32_t collide_type_in, uint32_t collide_mask_in,
CollideModel* collide_model_in = nullptr, uint32_t flags = 0);
~RigidBody() override;
auto body() const -> dBodyID { return body_; }
auto geom(int i = 0) const -> dGeomID { return geoms_[i]; }
// Draw a representation of the rigid body for debugging.
void Draw(RenderPass* pass, bool shaded = true);
auto part() const -> Part* {
assert(part_.exists());
return part_.get();
}
void Wake() {
if (body_) {
dBodyEnable(body_);
}
}
void AddCallback(CollideCallbackFunc callback_in, void* data_in);
auto CallCollideCallbacks(dContact* contacts, int count,
RigidBody* opposingbody) -> bool;
void SetDimensions(
float d1, float d2 = 0.0f, float d3 = 0.0f, // body dimensions
float m1 = 0.0f, float m2 = 0.0f,
float m3 = 0.0f, // Mass dimensions (default to regular if zero).
float density = 1.0f);
// If geomWakeOnCollide is true, a GEOM_ONLY object colliding with a sleeping
// body will wake it up. Generally this should be true if the geom is moving
// or changing.
void set_geom_wake_on_collide(bool enable) { geom_wake_on_collide_ = enable; }
auto geom_wake_on_collide() const -> bool { return geom_wake_on_collide_; }
auto id() const -> int { return id_; }
void ApplyGlobalImpulse(float px, float py, float pz, float fx, float fy,
float fz);
auto ApplyImpulse(float px, float py, float pz, float vx, float vy, float vz,
float fdirx, float fdiry, float fdirz, float mag,
float v_mag, float radiusm, bool calc_only) -> float;
void KillConstraints();
// Rigid body joint wrapper. This takes ownership of joints it is passed
// all joints should use this mechanism so they are automatically
// cleaned up when bodies are destroyed.
class Joint {
public:
Joint();
~Joint();
// Attach this wrapper to a new ode joint.
// If already attached to a joint, that joint is first killed.
void SetJoint(dxJointFixed* id, Scene* sg);
// Returns the ode joint id or nullptr if it has been killed
// (by the other body dying, etc).
auto joint() const -> dJointID { return id_; }
// Always use this in place of dJointAttach to attach the joint to rigid
// bodies.
void AttachToBodies(RigidBody* b1, RigidBody* b2);
void Kill(); // Kills the joint if it is valid.
// Whether joint still exists.
auto IsAlive() const -> bool { return id_ != nullptr; }
private:
millisecs_t creation_time_{};
dxJointFixed* id_{};
RigidBody* b1_{};
RigidBody* b2_{};
};
// Used by Joint.
void AddJoint(Joint* j) { joints_.push_back(j); }
void RemoveJoint(Joint* j) {
for (auto i = joints_.begin(); i != joints_.end(); i++) {
if ((*i) == j) {
joints_.erase(i);
return;
}
}
}
void Check();
auto type() const -> Type { return type_; }
auto collide_type() const -> uint32_t { return collide_type_; }
auto collide_mask() const -> uint32_t { return collide_mask_; }
auto flags() const -> uint32_t { return flags_; }
void set_flags(uint32_t flags) { flags_ = flags; }
auto can_cause_impact_damage() const -> bool {
return can_cause_impact_damage_;
}
void set_can_cause_impact_damage(bool val) { can_cause_impact_damage_ = val; }
// Applies to spheres.
auto radius() const -> float { return dimensions_[0]; }
auto GetTransform() -> Matrix44f;
void UpdateBlending();
void AddBlendOffset([[maybe_unused]] float x, float y, float z);
auto blend_offset() const -> const Vector3f& { return blend_offset_; }
private:
Vector3f blend_offset_{0.0f, 0.0f, 0.0f};
millisecs_t blend_time_{};
#if BA_DEBUG_BUILD
float prev_pos_[3]{};
float prev_vel_[3]{};
float prev_a_vel_[3]{};
#endif
millisecs_t creation_time_{};
bool can_cause_impact_damage_{};
Dynamics* dynamics_{};
uint32_t collide_type_{};
uint32_t collide_mask_{};
std::list<Joint*> joints_;
bool geom_wake_on_collide_{};
int id_{};
Object::Ref<CollideModel> collide_model_;
float dimensions_[3]{};
Type type_{};
Shape shape_{};
dBodyID body_{};
std::vector<dGeomID> geoms_;
millisecs_t birth_time_{};
Object::WeakRef<Part> part_;
struct CollideCallback {
CollideCallbackFunc callback;
void* data;
};
std::vector<CollideCallback> collide_callbacks_;
uint32_t flags_{};
};
} // namespace ballistica
#endif // BALLISTICA_DYNAMICS_RIGID_BODY_H_

View File

@ -0,0 +1,145 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_GAME_CONNECTION_CONNECTION_H_
#define BALLISTICA_GAME_CONNECTION_CONNECTION_H_
#include <map>
#include <string>
#include <vector>
#include "ballistica/core/object.h"
#include "ballistica/game/player_spec.h"
#include "ballistica/python/python_ref.h"
namespace ballistica {
// Start near the top of the range to make sure looping works as expected.
const int kFirstConnectionStateNum = 65520;
/// Connection to a remote session; either as a host or client.
class Connection : public Object {
public:
Connection();
// Send a reliable message to the client
// these will always be delivered in the order sent
void SendReliableMessage(const std::vector<uint8_t>& data);
// Send an unreliable message to the client; these are not guaranteed
// to be delivered, but when they are, they're delivered properly in order
// between other unreliable/reliable messages.
void SendUnreliableMessage(const std::vector<uint8_t>& data);
// Send a json-based reliable message.
void SendJMessage(cJSON* val);
virtual void Update();
// Called with raw packets as they come in from the network.
virtual void HandleGamePacket(const std::vector<uint8_t>& buffer);
// Called when the next in-order message is available.
virtual void HandleMessagePacket(const std::vector<uint8_t>& buffer) = 0;
// Request an orderly disconnect.
virtual void RequestDisconnect() = 0;
auto GetBytesOutPerSecond() const -> int64_t { return last_bytes_out_; }
auto GetBytesOutPerSecondCompressed() const -> int64_t {
return last_bytes_out_compressed_;
}
auto GetMessagesOutPerSecond() const -> int64_t {
return last_packet_count_out_;
}
auto GetMessageResendsPerSecond() const -> int64_t {
return last_resend_packet_count_;
}
auto GetBytesInPerSecond() const -> int64_t { return last_bytes_in_; }
auto GetBytesInPerSecondCompressed() const -> int64_t {
return last_bytes_in_compressed_;
}
auto GetMessagesInPerSecond() const -> int64_t {
return last_packet_count_in_;
}
auto GetBytesResentPerSecond() const -> int64_t {
return last_resend_bytes_out_;
}
auto average_ping() const -> float { return average_ping_; }
auto can_communicate() const -> bool { return can_communicate_; }
auto peer_spec() const -> const PlayerSpec& { return peer_spec_; }
void HandleGamePacketCompressed(const std::vector<uint8_t>& data);
auto errored() const -> bool { return errored_; }
auto creation_time() const -> millisecs_t { return creation_time_; }
auto multipart_buffer_size() const -> size_t {
return multipart_buffer_.size();
}
protected:
void SendGamePacket(const std::vector<uint8_t>& data);
virtual void SendGamePacketCompressed(const std::vector<uint8_t>& data) = 0;
void ErrorSilent() { Error(""); }
virtual void Error(const std::string& error_msg);
void set_peer_spec(const PlayerSpec& spec) { peer_spec_ = spec; }
void set_can_communicate(bool val) { can_communicate_ = val; }
void set_connection_dying(bool val) { connection_dying_ = val; }
void set_errored(bool val) { errored_ = val; }
private:
void ProcessWaitingMessages();
void HandleResends(millisecs_t real_time, const std::vector<uint8_t>& data,
int offset);
void EmbedAcks(millisecs_t real_time, std::vector<uint8_t>* data, int offset);
std::vector<uint8_t> multipart_buffer_;
struct ReliableMessageIn {
std::vector<uint8_t> data;
millisecs_t arrival_time;
};
struct ReliableMessageOut {
std::vector<uint8_t> data;
millisecs_t first_send_time;
millisecs_t last_send_time;
millisecs_t resend_time;
bool acked;
};
// Leaf classes should set this when they start dying.
// This prevents any SendGamePacketCompressed() calls from happening.
bool connection_dying_{};
float average_ping_{};
int64_t last_resend_bytes_out_{};
int64_t last_bytes_out_{};
int64_t last_bytes_out_compressed_{};
int64_t bytes_out_{};
int64_t bytes_out_compressed_{};
int64_t resend_bytes_out_{};
int64_t last_packet_count_out_{};
int64_t last_resend_packet_count_{};
int64_t resend_packet_count_{};
int64_t packet_count_out_{};
int64_t last_bytes_in_{};
int64_t last_bytes_in_compressed_{};
int64_t bytes_in_{};
int64_t bytes_in_compressed_{};
int64_t last_packet_count_in_{};
int64_t packet_count_in_{};
millisecs_t last_average_update_time_{};
millisecs_t creation_time_{};
PlayerSpec peer_spec_; // Name of the account/device on the other end.
std::map<uint16_t, ReliableMessageIn> in_messages_;
std::map<uint16_t, ReliableMessageOut> out_messages_;
bool can_communicate_{};
bool errored_{};
millisecs_t last_prune_time_{};
millisecs_t last_ack_send_time_{};
// These are explicitly 16 bit values.
uint16_t next_out_message_num_ = kFirstConnectionStateNum;
uint16_t next_out_unreliable_message_num_{};
uint16_t next_in_message_num_ = kFirstConnectionStateNum;
uint16_t next_in_unreliable_message_num_{};
};
} // namespace ballistica
#endif // BALLISTICA_GAME_CONNECTION_CONNECTION_H_

View File

@ -0,0 +1,76 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_GAME_CONNECTION_CONNECTION_TO_CLIENT_H_
#define BALLISTICA_GAME_CONNECTION_CONNECTION_TO_CLIENT_H_
#include <map>
#include <string>
#include <vector>
#include "ballistica/game/connection/connection.h"
namespace ballistica {
/// Connection to a party client if we're the host.
class ConnectionToClient : public Connection {
public:
explicit ConnectionToClient(int id);
~ConnectionToClient() override;
void Update() override;
void HandleMessagePacket(const std::vector<uint8_t>& buffer) override;
void HandleGamePacket(const std::vector<uint8_t>& buffer) override;
auto id() const -> int { return id_; }
// More efficient than dynamic_cast (hmm do we still want this?).
virtual auto GetAsUDP() -> ConnectionToClientUDP*;
void SetController(ClientControllerInterface* c);
auto GetPlayerProfiles() const -> PyObject* { return player_profiles_.get(); }
auto build_number() const -> int { return build_number_; }
void SendScreenMessage(const std::string& s, float r = 1.0f, float g = 1.0f,
float b = 1.0f);
auto token() const -> const std::string& { return token_; }
void HandleMasterServerClientInfo(PyObject* info_obj);
/// Return the public id for this client. If they have not been verified
/// by the master-server, returns an empty string.
auto peer_public_account_id() const -> const std::string& {
return peer_public_account_id_;
}
/// Return whether this client is an admin. Will only return true once their
/// account id has been verified by the master server.
auto IsAdmin() const -> bool;
private:
virtual auto ShouldPrintIncompatibleClientErrors() const -> bool;
// Returns a spec for this client that incorporates their player names
// or their peer name if they have no players.
auto GetCombinedSpec() -> PlayerSpec;
auto GetClientInputDevice(int remote_id) -> ClientInputDevice*;
void Error(const std::string& error_msg) override;
std::string our_handshake_player_spec_str_;
std::string our_handshake_salt_;
std::string peer_public_account_id_;
ClientControllerInterface* controller_ = nullptr;
std::map<int, ClientInputDevice*> client_input_devices_;
millisecs_t last_hand_shake_send_time_ = 0;
int id_ = -1;
int build_number_ = 0;
bool got_client_info_ = false;
bool kick_voted_ = false;
bool kick_vote_choice_ = false;
std::string token_;
std::string peer_hash_;
PythonRef player_profiles_;
bool got_info_from_master_server_ = false;
std::vector<millisecs_t> last_chat_times_;
millisecs_t next_kick_vote_allow_time_ = 0;
millisecs_t chat_block_time_ = 0;
millisecs_t last_remove_player_time_ = -99999;
int next_chat_block_seconds_ = 10;
friend class Game;
};
} // namespace ballistica
#endif // BALLISTICA_GAME_CONNECTION_CONNECTION_TO_CLIENT_H_

View File

@ -0,0 +1,40 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_GAME_CONNECTION_CONNECTION_TO_CLIENT_UDP_H_
#define BALLISTICA_GAME_CONNECTION_CONNECTION_TO_CLIENT_UDP_H_
#include <memory>
#include <string>
#include <vector>
#include "ballistica/game/connection/connection_to_client.h"
#include "ballistica/networking/networking.h"
namespace ballistica {
// Connection to a party client if we're the host.
class ConnectionToClientUDP : public ConnectionToClient {
public:
ConnectionToClientUDP(const SockAddr& addr, std::string client_name,
uint8_t request_id, int client_id);
~ConnectionToClientUDP() override;
void Update() override;
void HandleGamePacket(const std::vector<uint8_t>& buffer) override;
auto client_name() const -> const std::string& { return client_name_; }
auto GetAsUDP() -> ConnectionToClientUDP* override;
void RequestDisconnect() override;
protected:
uint8_t request_id_;
std::unique_ptr<SockAddr> addr_;
std::string client_name_;
bool did_die_;
void Die();
void SendDisconnectRequest();
millisecs_t last_client_response_time_;
void SendGamePacketCompressed(const std::vector<uint8_t>& data) override;
};
} // namespace ballistica
#endif // BALLISTICA_GAME_CONNECTION_CONNECTION_TO_CLIENT_UDP_H_

View File

@ -0,0 +1,47 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_GAME_CONNECTION_CONNECTION_TO_HOST_H_
#define BALLISTICA_GAME_CONNECTION_CONNECTION_TO_HOST_H_
#include <string>
#include <vector>
#include "ballistica/game/connection/connection.h"
namespace ballistica {
// connection to the party host if we're a client
class ConnectionToHost : public Connection {
public:
ConnectionToHost();
~ConnectionToHost() override;
void Update() override;
void HandleMessagePacket(const std::vector<uint8_t>& buffer) override;
void HandleGamePacket(const std::vector<uint8_t>& buffer) override;
// more efficient than dynamic_cast?.. bad idea?..
virtual auto GetAsUDP() -> ConnectionToHostUDP*;
auto build_number() const -> int { return build_number_; }
auto protocol_version() const -> int { return protocol_version_; }
void set_protocol_version(int val) { protocol_version_ = val; }
auto party_name() const -> std::string {
// FIXME should we return peer name as fallback?..
return party_name_;
}
private:
std::string party_name_;
std::string peer_hash_input_;
std::string peer_hash_;
bool printed_connect_message_ = false;
int protocol_version_ = kProtocolVersion;
int build_number_ = 0;
bool got_host_info_ = false;
// can remove once back-compat protocol is > 29
bool ignore_old_attach_remote_player_packets_ = false;
// the client-session that we're driving
Object::WeakRef<ClientSession> client_session_;
};
} // namespace ballistica
#endif // BALLISTICA_GAME_CONNECTION_CONNECTION_TO_HOST_H_

View File

@ -0,0 +1,50 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_GAME_CONNECTION_CONNECTION_TO_HOST_UDP_H_
#define BALLISTICA_GAME_CONNECTION_CONNECTION_TO_HOST_UDP_H_
#include <memory>
#include <string>
#include <vector>
#include "ballistica/game/connection/connection_to_host.h"
#include "ballistica/networking/networking.h"
namespace ballistica {
class ConnectionToHostUDP : public ConnectionToHost {
public:
explicit ConnectionToHostUDP(const SockAddr& addr);
~ConnectionToHostUDP() override;
void Update() override;
void HandleGamePacket(const std::vector<uint8_t>& buffer) override;
auto GetAsUDP() -> ConnectionToHostUDP* override;
auto request_id() const -> uint8_t { return request_id_; }
void set_client_id(int val) { client_id_ = val; }
auto client_id() const -> int { return client_id_; }
// Attempt connecting via a different protocol. If none are left to try,
// returns false.
auto SwitchProtocol() -> bool;
void RequestDisconnect() override;
protected:
uint8_t request_id_{};
std::unique_ptr<SockAddr> addr_;
bool did_die_{};
void Die();
void SendDisconnectRequest();
millisecs_t last_client_i_d_request_time_{};
millisecs_t last_disconnect_request_time_{};
int client_id_{};
millisecs_t last_host_response_time_{};
void SendGamePacketCompressed(const std::vector<uint8_t>& data) override;
void Error(const std::string& error_msg) override;
private:
void GetRequestID();
};
} // namespace ballistica
#endif // BALLISTICA_GAME_CONNECTION_CONNECTION_TO_HOST_UDP_H_

View File

@ -0,0 +1,86 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_PLATFORM_APPLE_PLATFORM_APPLE_H_
#define BALLISTICA_PLATFORM_APPLE_PLATFORM_APPLE_H_
#if BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS
#include <list>
#include <mutex>
#include <string>
#include <vector>
#include "ballistica/platform/platform.h"
namespace ballistica {
class PlatformApple : public Platform {
public:
PlatformApple();
auto GetDeviceUUIDPrefix() -> std::string override;
auto GetRealDeviceUUID(std::string* uuid) -> bool override;
auto GenerateUUID() -> std::string override;
auto GetDefaultConfigDir() -> std::string override;
auto GetLocale() -> std::string override;
auto DoGetDeviceName() -> std::string override;
auto DoHasTouchScreen() -> bool override;
auto GetInterfaceType() -> UIScale override;
auto IsRunningOnDesktop() -> bool override;
void HandleLog(const std::string& msg) override;
void SetupDataDirectory() override;
void GetTextBoundsAndWidth(const std::string& text, Rect* r,
float* width) override;
void FreeTextTexture(void* tex) override;
auto CreateTextTexture(int width, int height,
const std::vector<std::string>& strings,
const std::vector<float>& positions,
const std::vector<float>& widths, float scale)
-> void* override;
auto GetTextTextureData(void* tex) -> uint8_t* override;
void GetFriendScores(const std::string& game, const std::string& game_version,
void* py_callback) override;
void SubmitScore(const std::string& game, const std::string& version,
int64_t score) override;
void ReportAchievement(const std::string& achievement) override;
auto HaveLeaderboard(const std::string& game, const std::string& config)
-> bool override;
void ShowOnlineScoreUI(const std::string& show, const std::string& game,
const std::string& game_version) override;
void Purchase(const std::string& item) override;
void RestorePurchases() override;
auto NewAutoReleasePool() -> void* override;
void DrainAutoReleasePool(void* pool) override;
void DoOpenURL(const std::string& url) override;
void ResetAchievements() override;
void GameCenterLogin() override;
void PurchaseAck(const std::string& purchase,
const std::string& order_id) override;
auto IsOSPlayingMusic() -> bool override;
void SetHardwareCursorVisible(bool visible) override;
void QuitApp() override;
void GetScoresToBeat(const std::string& level, const std::string& config,
void* py_callback) override;
void OpenFileExternally(const std::string& path) override;
void OpenDirExternally(const std::string& path) override;
void MacMusicAppInit() override;
auto MacMusicAppGetVolume() -> int override;
void MacMusicAppSetVolume(int volume) override;
void MacMusicAppGetLibrarySource() override;
void MacMusicAppStop() override;
auto MacMusicAppPlayPlaylist(const std::string& playlist) -> bool override;
auto MacMusicAppGetPlaylists() -> std::list<std::string> override;
void StartListeningForWiiRemotes() override;
void StopListeningForWiiRemotes() override;
auto IsEventPushMode() -> bool override;
auto ContainsPythonDist() -> bool override;
auto GetPlatformName() -> std::string override;
auto GetSubplatformName() -> std::string override;
private:
// std::mutex log_mutex_;
// std::string log_line_;
};
} // namespace ballistica
#endif // BA_XCODE_BUILD || BA_OSTYPE_MACOS
#endif // BALLISTICA_PLATFORM_APPLE_PLATFORM_APPLE_H_

View File

@ -1,610 +0,0 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/python/methods/python_methods_networking.h"
#include <set>
#include <string>
#include <vector>
#include "ballistica/app/app_globals.h"
#include "ballistica/game/connection/connection_to_host.h"
#include "ballistica/game/game.h"
#include "ballistica/math/vector3f.h"
#include "ballistica/networking/master_server_config.h"
#include "ballistica/networking/network_reader.h"
#include "ballistica/networking/networking.h"
#include "ballistica/networking/sockaddr.h"
#include "ballistica/networking/telnet_server.h"
#include "ballistica/platform/platform.h"
#include "ballistica/python/python.h"
namespace ballistica {
// Ignore signed bitwise stuff; python macros do it quite a bit.
#pragma clang diagnostic push
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
auto PyGetPublicPartyEnabled(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("getpublicpartyenabled");
static const char* kwlist[] = {nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
const_cast<char**>(kwlist)))
return nullptr;
assert(g_python);
if (g_game->public_party_enabled()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
BA_PYTHON_CATCH;
}
auto PySetPublicPartyEnabled(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("setpublicpartyenabled");
int enable;
static const char* kwlist[] = {"enabled", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
const_cast<char**>(kwlist), &enable)) {
return nullptr;
}
assert(g_python);
g_game->SetPublicPartyEnabled(static_cast<bool>(enable));
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PySetPublicPartyName(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("setpublicpartyname");
PyObject* name_obj;
static const char* kwlist[] = {"name", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
const_cast<char**>(kwlist), &name_obj)) {
return nullptr;
}
std::string name = Python::GetPyString(name_obj);
assert(g_python);
g_game->SetPublicPartyName(name);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PySetPublicPartyStatsURL(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("setpublicpartystatsurl");
PyObject* url_obj;
static const char* kwlist[] = {"url", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
const_cast<char**>(kwlist), &url_obj)) {
return nullptr;
}
// The call expects an empty string for the no-url option.
std::string url = (url_obj == Py_None) ? "" : Python::GetPyString(url_obj);
assert(g_python);
g_game->SetPublicPartyStatsURL(url);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyGetPublicPartyMaxSize(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("getpublicpartymaxsize");
static const char* kwlist[] = {nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
const_cast<char**>(kwlist))) {
return nullptr;
}
assert(g_python);
return PyLong_FromLong(g_game->public_party_max_size());
BA_PYTHON_CATCH;
}
auto PySetPublicPartyMaxSize(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("setpublicpartymaxsize");
int max_size;
static const char* kwlist[] = {"max_size", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i",
const_cast<char**>(kwlist), &max_size)) {
return nullptr;
}
assert(g_python);
g_game->SetPublicPartyMaxSize(max_size);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PySetAuthenticateClients(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("set_authenticate_clients");
int enable;
static const char* kwlist[] = {"enable", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
const_cast<char**>(kwlist), &enable)) {
return nullptr;
}
assert(g_game);
g_game->set_require_client_authentication(static_cast<bool>(enable));
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PySetAdmins(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("set_admins");
PyObject* admins_obj;
static const char* kwlist[] = {"admins", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
const_cast<char**>(kwlist), &admins_obj)) {
return nullptr;
}
assert(g_game);
auto admins = Python::GetPyStrings(admins_obj);
std::set<std::string> adminset;
for (auto&& admin : admins) {
adminset.insert(admin);
}
g_game->set_admin_public_ids(adminset);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PySetEnableDefaultKickVoting(PyObject* self, PyObject* args,
PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("set_enable_default_kick_voting");
int enable;
static const char* kwlist[] = {"enable", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
const_cast<char**>(kwlist), &enable)) {
return nullptr;
}
assert(g_game);
g_game->set_kick_voting_enabled(static_cast<bool>(enable));
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyConnectToParty(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("connect_to_party");
std::string address;
PyObject* address_obj;
int port = kDefaultPort;
// Whether we should print standard 'connecting...' and 'party full..'
// messages when false, only odd errors such as version incompatibility will
// be printed and most connection attempts will be silent todo: could
// generalize this to pass all results to a callback instead
int print_progress = 1;
static const char* kwlist[] = {"address", "port", "print_progress", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|ip",
const_cast<char**>(kwlist), &address_obj,
&port, &print_progress)) {
return nullptr;
}
address = Python::GetPyString(address_obj);
// Disallow in headless build (people were using this for spam-bots).
if (HeadlessMode()) {
throw Exception("Not available in headless mode.");
}
SockAddr s;
try {
s = SockAddr(address, port);
// HACK: CLion currently flags our catch clause as unreachable even
// though SockAddr constructor can throw exceptions. Work around that here.
if (explicit_bool(false)) {
throw Exception();
}
} catch (const std::exception&) {
ScreenMessage(g_game->GetResourceString("invalidAddressErrorText"),
{1, 0, 0});
Py_RETURN_NONE;
}
g_game->PushHostConnectedUDPCall(s, static_cast<bool>(print_progress));
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyAcceptPartyInvitation(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("accept_party_invitation");
const char* invite_id;
static const char* kwlist[] = {"invite_id", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
const_cast<char**>(kwlist), &invite_id)) {
return nullptr;
}
g_platform->AndroidGPGSPartyInviteAccept(invite_id);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyGetGooglePlayPartyClientCount(PyObject* self, PyObject* args,
PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("get_google_play_party_client_count");
BA_PRECONDITION(InGameThread());
#if BA_GOOGLE_BUILD
return PyLong_FromLong(g_game->GetGooglePlayClientCount());
#else
return PyLong_FromLong(0);
#endif
BA_PYTHON_CATCH;
}
auto PyClientInfoQueryResponse(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("client_info_query_response");
const char* token;
PyObject* response_obj;
static const char* kwlist[] = {"token", "response", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "sO",
const_cast<char**>(kwlist), &token,
&response_obj)) {
return nullptr;
}
g_game->SetClientInfoFromMasterServer(token, response_obj);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyGetConnectionToHostInfo(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("get_connection_to_host_info");
static const char* kwlist[] = {nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
const_cast<char**>(kwlist))) {
return nullptr;
}
ConnectionToHost* hc = g_game->connection_to_host();
if (hc) {
return Py_BuildValue("{sssi}", "name", hc->party_name().c_str(),
"build_number", hc->build_number());
} else {
return Py_BuildValue("{}");
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyDisconnectFromHost(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("disconnect_from_host");
static const char* kwlist[] = {nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
const_cast<char**>(kwlist))) {
return nullptr;
}
g_game->PushDisconnectFromHostCall();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyDisconnectClient(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("disconnect_client");
int client_id;
int ban_time = 300; // Old default before we exposed this.
static const char* kwlist[] = {"client_id", "ban_time", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|i",
const_cast<char**>(kwlist), &client_id,
&ban_time)) {
return nullptr;
}
bool kickable = g_game->DisconnectClient(client_id, ban_time);
if (kickable) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
BA_PYTHON_CATCH;
}
auto PyGetGamePort(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("get_game_port");
int port = 0;
if (g_network_reader != nullptr) {
// hmmm; we're just fetching the ipv4 port here;
// 6 could be different....
port = g_network_reader->port4();
}
return Py_BuildValue("i", port);
BA_PYTHON_CATCH;
}
auto PyGetMasterServerAddress(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("get_master_server_address");
int source = -1; // use default..
if (!PyArg_ParseTuple(args, "|i", &source)) {
return nullptr;
}
// source -1 implies to use current one
if (source == -1) {
source = g_app_globals->master_server_source;
}
const char* addr;
if (source == 0) {
addr = BA_MASTER_SERVER_DEFAULT_ADDR;
} else if (source == 1) {
addr = BA_MASTER_SERVER_FALLBACK_ADDR;
} else {
BA_LOG_ONCE("Error: Got unexpected source: " + std::to_string(source)
+ ".");
addr = BA_MASTER_SERVER_FALLBACK_ADDR;
}
return PyUnicode_FromString(addr);
BA_PYTHON_CATCH;
}
auto PySetMasterServerSource(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("set_master_server_source");
int source;
if (!PyArg_ParseTuple(args, "i", &source)) return nullptr;
if (source != 0 && source != 1) {
BA_LOG_ONCE("Error: Invalid server source: " + std::to_string(source)
+ ".");
source = 1;
}
g_app_globals->master_server_source = source;
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PySetTelnetAccessEnabled(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("set_telnet_access_enabled");
assert(InGameThread());
int enable;
static const char* kwlist[] = {"enable", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
const_cast<char**>(kwlist), &enable)) {
return nullptr;
}
if (g_app_globals->telnet_server) {
g_app_globals->telnet_server->SetAccessEnabled(static_cast<bool>(enable));
} else {
throw Exception("Telnet server not enabled.");
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyHostScanCycle(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("host_scan_cycle");
g_networking->HostScanCycle();
std::vector<Networking::ScanResultsEntry> results =
g_networking->GetScanResults();
PyObject* py_list = PyList_New(0);
for (auto&& i : results) {
PyList_Append(py_list, Py_BuildValue("{ssss}", "display_string",
i.display_string.c_str(), "address",
i.address.c_str()));
}
return py_list;
BA_PYTHON_CATCH;
}
auto PyEndHostScanning(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("end_host_scanning");
g_networking->EndHostScanning();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyHaveConnectedClients(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("have_connected_clients");
if (g_game->GetConnectedClientCount() > 0) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
BA_PYTHON_CATCH;
}
auto PyInvitePlayers(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("invite_players");
static const char* kwlist[] = {nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
const_cast<char**>(kwlist))) {
return nullptr;
}
g_platform->AndroidGPGSPartyInvitePlayers();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
PyMethodDef PythonMethodsNetworking::methods_def[] = {
{"invite_players", (PyCFunction)PyInvitePlayers,
METH_VARARGS | METH_KEYWORDS,
"invite_players() -> None\n"
"\n"
"(internal)"
"\n"
"Category: General Utility Functions"},
{"have_connected_clients", (PyCFunction)PyHaveConnectedClients,
METH_VARARGS | METH_KEYWORDS,
"have_connected_clients() -> bool\n"
"\n"
"(internal)\n"
"\n"
"Category: General Utility Functions"},
{"end_host_scanning", (PyCFunction)PyEndHostScanning,
METH_VARARGS | METH_KEYWORDS,
"end_host_scanning() -> None\n"
"\n"
"(internal)\n"
"\n"
"Category: General Utility Functions"},
{"host_scan_cycle", (PyCFunction)PyHostScanCycle,
METH_VARARGS | METH_KEYWORDS,
"host_scan_cycle() -> list\n"
"\n"
"(internal)"},
{"set_telnet_access_enabled", (PyCFunction)PySetTelnetAccessEnabled,
METH_VARARGS | METH_KEYWORDS,
"set_telnet_access_enabled(enable: bool)\n"
" -> None\n"
"\n"
"(internal)"},
{"set_master_server_source", PySetMasterServerSource, METH_VARARGS,
"set_master_server_source(source: int) -> None\n"
"\n"
"(internal)"},
{"get_master_server_address", PyGetMasterServerAddress, METH_VARARGS,
"get_master_server_address(source: int = -1) -> str\n"
"\n"
"(internal)\n"
"\n"
"Return the address of the master server."},
{"get_game_port", PyGetGamePort, METH_VARARGS,
"get_game_port() -> int\n"
"\n"
"(internal)\n"
"\n"
"Return the port ballistica is hosting on."},
{"disconnect_from_host", (PyCFunction)PyDisconnectFromHost,
METH_VARARGS | METH_KEYWORDS,
"disconnect_from_host() -> None\n"
"\n"
"(internal)\n"
"\n"
"Category: General Utility Functions"},
{"disconnect_client", (PyCFunction)PyDisconnectClient,
METH_VARARGS | METH_KEYWORDS,
"disconnect_client(client_id: int, ban_time: int = 300) -> bool\n"
"\n"
"(internal)"},
{"get_connection_to_host_info", (PyCFunction)PyGetConnectionToHostInfo,
METH_VARARGS | METH_KEYWORDS,
"get_connection_to_host_info() -> dict\n"
"\n"
"(internal)"},
{"client_info_query_response", (PyCFunction)PyClientInfoQueryResponse,
METH_VARARGS | METH_KEYWORDS,
"client_info_query_response(token: str, response: Any) -> None\n"
"\n"
"(internal)"},
{"get_google_play_party_client_count",
(PyCFunction)PyGetGooglePlayPartyClientCount, METH_VARARGS | METH_KEYWORDS,
"get_google_play_party_client_count() -> int\n"
"\n"
"(internal)"},
{"accept_party_invitation", (PyCFunction)PyAcceptPartyInvitation,
METH_VARARGS | METH_KEYWORDS,
"accept_party_invitation(invite_id: str) -> None\n"
"\n"
"(internal)"},
{"connect_to_party", (PyCFunction)PyConnectToParty,
METH_VARARGS | METH_KEYWORDS,
"connect_to_party(address: str, port: int = None,\n"
" print_progress: bool = True) -> None\n"
"\n"
"(internal)"},
{"set_authenticate_clients", (PyCFunction)PySetAuthenticateClients,
METH_VARARGS | METH_KEYWORDS,
"set_authenticate_clients(enable: bool) -> None\n"
"\n"
"(internal)"},
{"set_admins", (PyCFunction)PySetAdmins, METH_VARARGS | METH_KEYWORDS,
"set_admins(admins: List[str]) -> None\n"
"\n"
"(internal)"},
{"set_enable_default_kick_voting",
(PyCFunction)PySetEnableDefaultKickVoting, METH_VARARGS | METH_KEYWORDS,
"set_enable_default_kick_voting(enable: bool) -> None\n"
"\n"
"(internal)"},
{"set_public_party_max_size", (PyCFunction)PySetPublicPartyMaxSize,
METH_VARARGS | METH_KEYWORDS,
"set_public_party_max_size(max_size: int) -> None\n"
"\n"
"(internal)"},
{"get_public_party_max_size", (PyCFunction)PyGetPublicPartyMaxSize,
METH_VARARGS | METH_KEYWORDS,
"get_public_party_max_size() -> int\n"
"\n"
"(internal)"},
{"set_public_party_stats_url", (PyCFunction)PySetPublicPartyStatsURL,
METH_VARARGS | METH_KEYWORDS,
"set_public_party_stats_url(url: Optional[str]) -> None\n"
"\n"
"(internal)"},
{"set_public_party_name", (PyCFunction)PySetPublicPartyName,
METH_VARARGS | METH_KEYWORDS,
"set_public_party_name(name: str) -> None\n"
"\n"
"(internal)"},
{"set_public_party_enabled", (PyCFunction)PySetPublicPartyEnabled,
METH_VARARGS | METH_KEYWORDS,
"set_public_party_enabled(enabled: bool) -> None\n"
"\n"
"(internal)"},
{"get_public_party_enabled", (PyCFunction)PyGetPublicPartyEnabled,
METH_VARARGS | METH_KEYWORDS,
"get_public_party_enabled() -> bool\n"
"\n"
"(internal)"},
{nullptr, nullptr, 0, nullptr}};
#pragma clang diagnostic pop
} // namespace ballistica

View File

@ -1,18 +0,0 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_NETWORKING_H_
#define BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_NETWORKING_H_
#include "ballistica/python/python_sys.h"
namespace ballistica {
/// Networking related individual python methods for our module.
class PythonMethodsNetworking {
public:
static PyMethodDef methods_def[];
};
} // namespace ballistica
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_NETWORKING_H_

View File

@ -0,0 +1,136 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/anim_curve_node.h"
#include <algorithm>
#include <cmath>
#include <vector>
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
class AnimCurveNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS AnimCurveNode
BA_NODE_CREATE_CALL(CreateAnimCurve);
BA_FLOAT_ATTR(in, in, set_in);
BA_BOOL_ATTR(loop, loop, set_loop);
BA_INT64_ARRAY_ATTR(times, times, set_times);
BA_FLOAT_ARRAY_ATTR(values, values, set_values);
BA_FLOAT_ATTR(offset, offset, set_offset);
BA_FLOAT_ATTR_READONLY(out, GetOut);
#undef BA_NODE_TYPE_CLASS
AnimCurveNodeType()
: NodeType("animcurve", CreateAnimCurve),
in(this),
loop(this),
times(this),
values(this),
offset(this),
out(this) {}
};
static NodeType* node_type{};
auto AnimCurveNode::InitType() -> NodeType* {
node_type = new AnimCurveNodeType();
return node_type;
}
AnimCurveNode::AnimCurveNode(Scene* scene) : Node(scene, node_type) {}
AnimCurveNode::~AnimCurveNode() = default;
auto AnimCurveNode::GetOut() -> float {
// Recreate our keyframes if need be.
if (keys_dirty_) {
keyframes_.clear();
auto num = std::min(times_.size(), values_.size());
if (num < 1) {
input_start_ = 0;
input_end_ = 0;
}
for (size_t i = 0; i < num; i++) {
if (i == 0) {
input_start_ = times_[i];
}
if (i == (num - 1)) {
input_end_ = times_[i];
}
keyframes_.emplace_back(times_[i], values_[i]);
}
keys_dirty_ = false;
out_dirty_ = true;
}
// Now update out if need-be.
if (out_dirty_) {
float in_val = in_ - offset_;
if ((input_end_ - input_start_) > 0) {
if (keyframes_.size() < 2) {
assert(keyframes_.size() == 1);
out_ = keyframes_[0].value;
} else {
bool got;
if (loop_) {
in_val = fmodf(in_val, (input_end_ - input_start_));
if (in_val < 0) in_val += (input_end_ - input_start_);
got = false;
} else {
if (in_val >= input_end_) {
out_ = keyframes_.back().value;
got = true;
} else if (in_val <= input_start_) {
out_ = keyframes_.front().value;
got = true;
} else {
got = false;
}
}
if (!got) {
out_ = keyframes_[0].value;
// Ok we know we've got at least 2 keyframes.
auto i1 = keyframes_.begin();
auto i2 = keyframes_.begin();
auto i = keyframes_.begin();
while (true) {
if (i == keyframes_.end()) {
break;
}
if (i->time < in_val) {
i++;
i1 = i2;
i2 = i;
} else {
break;
}
}
if (i2->time - i1->time == 0) {
out_ = i1->value;
} else {
out_ = i1->value
+ ((in_val - i1->time)
/ static_cast<float>(i2->time - i1->time))
* (i2->value - i1->value);
}
}
}
} else {
// No keyframes?.. hmm just go with 0.
if (keyframes_.empty()) {
out_ = 0.0f;
} else {
// We have one keyframe; hmm what to do.
out_ = keyframes_[0].value;
}
}
out_dirty_ = false;
}
return out_;
}
} // namespace ballistica

View File

@ -0,0 +1,75 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_ANIM_CURVE_NODE_H_
#define BALLISTICA_SCENE_NODE_ANIM_CURVE_NODE_H_
#include <vector>
#include "ballistica/scene/node/node.h"
namespace ballistica {
// Node containing a keyframe graph associating an input value with an output
// value.
class AnimCurveNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit AnimCurveNode(Scene* scene);
~AnimCurveNode() override;
auto in() const -> float { return in_; }
void set_in(float value) {
in_ = value;
out_dirty_ = true;
}
auto loop() const -> bool { return loop_; }
void set_loop(bool val) {
loop_ = val;
out_dirty_ = true;
}
auto times() const -> const std::vector<millisecs_t>& { return times_; }
void set_times(const std::vector<millisecs_t>& vals) {
times_ = vals;
keys_dirty_ = true;
}
auto values() const -> const std::vector<float>& { return values_; }
void set_values(const std::vector<float>& vals) {
values_ = vals;
keys_dirty_ = true;
}
auto offset() const -> float { return offset_; }
void set_offset(float val) {
offset_ = val;
out_dirty_ = true;
}
auto GetOut() -> float;
private:
struct Keyframe {
Keyframe(uint32_t t, float v) : time(t), value(v) {}
uint32_t time;
float value;
};
float in_ = 0.0f;
std::vector<millisecs_t> times_;
std::vector<float> values_;
bool keys_dirty_ = true;
bool out_dirty_ = true;
float out_ = 0.0f;
bool loop_ = true;
std::vector<Keyframe> keyframes_;
float input_start_ = 0.0f;
float input_end_ = 0.0f;
float offset_ = 0.0f;
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_ANIM_CURVE_NODE_H_

View File

@ -0,0 +1,85 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/bomb_node.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/media/component/collide_model.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
const float kFuseOffset = 0.35f;
// Returns noise value between 0 and 1.
// TODO(ericf): Need to interpolate between 2 values.
static auto SimpleNoise(uint32_t x) -> float {
x = (x << 13u) ^ x;
return (0.5f
* static_cast<float>((x * (x * x * 15731u + 789221u) + 1376312589u)
& 0x7fffffffu)
/ 1073741824.0f);
}
class BombNodeType : public PropNodeType {
public:
#define BA_NODE_TYPE_CLASS BombNode
BA_NODE_CREATE_CALL(CreateBomb);
BA_FLOAT_ATTR(fuse_length, fuse_length, set_fuse_length);
#undef BA_NODE_TYPE_CLASS
BombNodeType() : PropNodeType("bomb", CreateBomb), fuse_length(this) {}
};
static NodeType* node_type{};
auto BombNode::InitType() -> NodeType* {
node_type = new BombNodeType();
return node_type;
}
BombNode::BombNode(Scene* scene) : PropNode(scene, node_type) {}
void BombNode::OnCreate() {
// We can't do this in our constructor because
// it would prevent the user from setting density/etc attrs.
// (user attrs get applied after constructors fire)
SetBody("sphere");
}
void BombNode::Step() {
PropNode::Step();
if (body_.exists()) {
// Update our fuse and light position.
dVector3 fuse_tip_pos;
dGeomGetRelPointPos(body_->geom(), 0, (fuse_length_ + kFuseOffset), 0,
fuse_tip_pos);
light_translate_ = fuse_tip_pos;
light_translate_.x += body_->blend_offset().x;
light_translate_.y += body_->blend_offset().y;
light_translate_.z += body_->blend_offset().z;
#if !BA_HEADLESS_BUILD
fuse_.SetTransform(Matrix44fTranslate(0, kFuseOffset * model_scale_, 0)
* body_->GetTransform());
fuse_.SetLength(fuse_length_);
#endif // !BA_HEADLESS_BUILD
}
}
void BombNode::Draw(FrameDef* frame_def) {
#if !BA_HEADLESS_BUILD
PropNode::Draw(frame_def);
float s_scale, s_density;
shadow_.GetValues(&s_scale, &s_density);
float intensity = SimpleNoise(static_cast<uint32_t>(id() + scene()->time()))
* s_density * 0.2f;
float s = 4.0f * s_scale;
float r = 1.5f * intensity;
float g = 0.1f * intensity;
float b = 0.1f * intensity;
float a = 0.0f;
g_graphics->DrawBlotchSoft(light_translate_, s, r, g, b, a);
g_graphics->DrawBlotchSoftObj(light_translate_, s, r, g, b, a);
#endif // !BA_HEADLESS_BUILD
}
} // namespace ballistica

View File

@ -0,0 +1,31 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_BOMB_NODE_H_
#define BALLISTICA_SCENE_NODE_BOMB_NODE_H_
#include "ballistica/dynamics/bg/bg_dynamics_fuse.h"
#include "ballistica/scene/node/prop_node.h"
namespace ballistica {
class BombNode : public PropNode {
public:
static auto InitType() -> NodeType*;
explicit BombNode(Scene* scene);
void Step() override;
void Draw(FrameDef* frame_def) override;
void OnCreate() override;
auto fuse_length() const -> float { return fuse_length_; }
void set_fuse_length(float val) { fuse_length_ = val; }
protected:
#if !BA_HEADLESS_BUILD
BGDynamicsFuse fuse_;
#endif
float fuse_length_ = 1.0f;
Vector3f light_translate_ = {0.0f, 0.0f, 0.0f};
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_BOMB_NODE_H_

View File

@ -0,0 +1,68 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/combine_node.h"
#include <algorithm>
#include <vector>
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
class CombineNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS CombineNode
BA_NODE_CREATE_CALL(CreateLocator);
BA_FLOAT_ATTR(input0, input_0, set_input_0);
BA_FLOAT_ATTR(input1, input_1, set_input_1);
BA_FLOAT_ATTR(input2, input_2, set_input_2);
BA_FLOAT_ATTR(input3, input_3, set_input_3);
BA_FLOAT_ARRAY_ATTR_READONLY(output, GetOutput);
BA_INT_ATTR(size, size, set_size);
#undef BA_NODE_TYPE_CLASS
CombineNodeType()
: NodeType("combine", CreateLocator),
input0(this),
input1(this),
input2(this),
input3(this),
output(this),
size(this) {}
};
static NodeType* node_type{};
auto CombineNode::InitType() -> NodeType* {
node_type = new CombineNodeType();
return node_type;
}
CombineNode::CombineNode(Scene* scene) : Node(scene, node_type) {}
auto CombineNode::GetOutput() -> std::vector<float> {
if (dirty_) {
if (do_size_unset_warning_) {
do_size_unset_warning_ = false;
BA_LOG_ONCE("ERROR: CombineNode size unset for " + label());
}
int actual_size = std::min(4, std::max(0, size_));
output_.resize(static_cast<size_t>(actual_size));
if (size_ > 0) {
output_[0] = input_0_;
}
if (size_ > 1) {
output_[1] = input_1_;
}
if (size_ > 2) {
output_[2] = input_2_;
}
if (size_ > 3) {
output_[3] = input_3_;
}
dirty_ = false;
}
return output_;
}
} // namespace ballistica

View File

@ -0,0 +1,58 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_COMBINE_NODE_H_
#define BALLISTICA_SCENE_NODE_COMBINE_NODE_H_
#include <vector>
#include "ballistica/scene/node/node.h"
namespace ballistica {
// An node used to combine individual input values into one array output value
class CombineNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit CombineNode(Scene* scene);
auto size() const -> int { return size_; }
void set_size(int val) {
size_ = val;
dirty_ = true;
do_size_unset_warning_ = false;
}
auto input_0() const -> float { return input_0_; }
void set_input_0(float val) {
input_0_ = val;
dirty_ = true;
}
auto input_1() const -> float { return input_1_; }
void set_input_1(float val) {
input_1_ = val;
dirty_ = true;
}
auto input_2() const -> float { return input_2_; }
void set_input_2(float val) {
input_2_ = val;
dirty_ = true;
}
auto input_3() const -> float { return input_3_; }
void set_input_3(float val) {
input_3_ = val;
dirty_ = true;
}
auto GetOutput() -> std::vector<float>;
private:
bool do_size_unset_warning_ = true;
float input_0_ = 0.0f;
float input_1_ = 0.0f;
float input_2_ = 0.0f;
float input_3_ = 0.0f;
int size_ = 4;
std::vector<float> output_;
bool dirty_ = true;
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_COMBINE_NODE_H_

View File

@ -0,0 +1,224 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/explosion_node.h"
#include "ballistica/dynamics/dynamics.h"
#include "ballistica/graphics/camera.h"
#include "ballistica/graphics/component/object_component.h"
#include "ballistica/graphics/component/post_process_component.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/graphics/renderer.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
class ExplosionNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS ExplosionNode
BA_NODE_CREATE_CALL(CreateExplosion);
BA_FLOAT_ARRAY_ATTR(position, position, set_position);
BA_FLOAT_ARRAY_ATTR(velocity, velocity, set_velocity);
BA_FLOAT_ATTR(radius, radius, set_radius);
BA_FLOAT_ARRAY_ATTR(color, color, set_color);
BA_BOOL_ATTR(big, big, set_big);
#undef BA_NODE_TYPE_CLASS
ExplosionNodeType()
: NodeType("explosion", CreateExplosion),
position(this),
velocity(this),
radius(this),
color(this),
big(this) {}
};
static NodeType* node_type{};
auto ExplosionNode::InitType() -> NodeType* {
node_type = new ExplosionNodeType();
return node_type;
}
ExplosionNode* gExplosionDistortLock = nullptr;
ExplosionNode::ExplosionNode(Scene* scene)
: Node(scene, node_type), birth_time_(scene->time()) {}
ExplosionNode::~ExplosionNode() {
if (draw_distortion_ && have_distortion_lock_) {
assert(gExplosionDistortLock == this);
gExplosionDistortLock = nullptr;
}
}
void ExplosionNode::set_big(bool val) {
big_ = val;
// big explosions try to steal the distortion pointer..
if (big_) {
check_draw_distortion_ = true;
}
}
void ExplosionNode::set_position(const std::vector<float>& vals) {
if (vals.size() != 3)
throw Exception("Expected float array of size 3 for position");
position_ = vals;
}
void ExplosionNode::set_velocity(const std::vector<float>& vals) {
if (vals.size() != 3)
throw Exception("Expected float array of size 3 for velocity");
velocity_ = vals;
}
void ExplosionNode::set_color(const std::vector<float>& vals) {
if (vals.size() != 3)
throw Exception("Expected float array of size 3 for color");
color_ = vals;
}
void ExplosionNode::Step() {
// update our position from our velocity
if (velocity_[0] != 0.0f || velocity_[1] != 0.0f || velocity_[2] != 0.0f) {
velocity_[0] *= 0.95f;
velocity_[1] *= 0.95f;
velocity_[2] *= 0.95f;
position_[0] += velocity_[0] * kGameStepSeconds;
position_[1] += velocity_[1] * kGameStepSeconds;
position_[2] += velocity_[2] * kGameStepSeconds;
}
}
void ExplosionNode::Draw(FrameDef* frame_def) {
{
bool high_quality = (frame_def->quality() >= GraphicsQuality::kHigh);
// we only draw distortion if we're the only bomb..
// (it gets expensive..)
if (check_draw_distortion_) {
check_draw_distortion_ = false;
{
if (big_) {
// Steal distortion handle.
if (gExplosionDistortLock != nullptr) {
gExplosionDistortLock->draw_distortion_ = false;
gExplosionDistortLock = this;
have_distortion_lock_ = true;
draw_distortion_ = true;
}
} else {
// Play nice and only distort if no one else currently is.
if (gExplosionDistortLock == nullptr) {
draw_distortion_ = true;
gExplosionDistortLock = this;
have_distortion_lock_ = true;
} else {
draw_distortion_ = false;
}
}
}
}
if (draw_distortion_) {
float age = scene()->time() - static_cast<float>(birth_time_);
float amt = (1.0f - 0.00265f * age);
if (amt > 0.0001f) {
amt = pow(amt, 2.2f);
amt *= 2.0f;
if (big_) {
amt *= 4.0f;
} else {
amt *= 0.8f;
}
float s = 1.0f;
if (high_quality) {
PostProcessComponent c(frame_def->blit_pass());
c.setNormalDistort(0.5f * amt);
c.PushTransform();
c.Translate(position_[0], position_[1], position_[2]);
c.Scale(1.0f + s * 0.8f * 0.025f * age,
1.0f + s * 0.8f * 0.0015f * age,
1.0f + s * 0.8f * 0.025f * age);
c.Scale(0.7f, 0.7f, 0.7f);
c.DrawModel(g_media->GetModel(SystemModelID::kShockWave),
kModelDrawFlagNoReflection);
c.PopTransform();
c.Submit();
} else {
// simpler transparent shock wave
// draw our distortion wave in the overlay pass
ObjectComponent c(frame_def->beauty_pass());
c.SetTransparent(true);
c.SetLightShadow(LightShadowType::kNone);
// eww hacky - the shock wave model uses color as distortion amount
c.SetColor(1.0f, 0.7f, 0.7f, 0.06f * amt);
c.PushTransform();
c.Translate(position_[0], position_[1], position_[2]);
c.Scale(1.0f + s * 0.8f * 0.025f * age,
1.0f + s * 0.8f * 0.0015f * age,
1.0f + s * 0.8f * 0.025f * age);
c.Scale(0.7f, 0.7f, 0.7f);
c.DrawModel(g_media->GetModel(SystemModelID::kShockWave),
kModelDrawFlagNoReflection);
c.PopTransform();
c.Submit();
}
}
}
}
float life = 1.0f * (big_ ? 350.0f : 260.0f);
float age = scene()->time() - static_cast<float>(birth_time_);
if (age < life) {
float b = 2.0f;
if (big_) {
b = 2.0f;
}
float o = age / life;
if (big_) {
o = pow(1.0f - o, 1.4f);
} else {
o = pow(1.0f - o, 0.8f);
}
float s = 1.0f - (age / life);
s = 1.0f - (s * s);
s *= radius_;
if (big_) {
s *= 2.0f;
} else {
s *= 1.2f;
}
s *= 0.75f;
float cx, cy, cz;
g_graphics->camera()->get_position(&cx, &cy, &cz);
ObjectComponent c(frame_def->beauty_pass());
c.SetTransparent(true);
c.SetLightShadow(LightShadowType::kNone);
c.SetPremultiplied(true);
c.SetTexture(g_media->GetTexture(SystemTextureID::kExplosion));
c.SetColor(1.3f * o * color_[0] * b, o * color_[1] * b, o * color_[2] * b,
0.0f);
c.PushTransform();
Vector3f to_cam =
Vector3f(cx - position_[0], cy - position_[1], cz - position_[2])
.Normalized();
Matrix44f m = Matrix44fTranslate(position_[0], position_[1], position_[2]);
Vector3f right = Vector3f::Cross(to_cam, kVector3fY).Normalized();
Vector3f up = Vector3f::Cross(right, to_cam).Normalized();
Matrix44f om = Matrix44fOrient(right, to_cam, up);
c.MultMatrix((om * m).m);
c.Scale(0.9f * s, 0.9f * s, 0.9f * s);
c.DrawModel(g_media->GetModel(SystemModelID::kShield),
kModelDrawFlagNoReflection);
c.Scale(0.6f, 0.6f, 0.6f);
c.Rotate(33, 0, 1, 0);
c.SetColor(o * 7.0f * color_[0], o * 7.0f * color_[1], o * 7.0f * color_[2],
0);
c.DrawModel(g_media->GetModel(SystemModelID::kShield),
kModelDrawFlagNoReflection);
c.PopTransform();
c.Submit();
}
}
} // namespace ballistica

View File

@ -0,0 +1,44 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_EXPLOSION_NODE_H_
#define BALLISTICA_SCENE_NODE_EXPLOSION_NODE_H_
#include <vector>
#include "ballistica/scene/node/node.h"
namespace ballistica {
class ExplosionNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit ExplosionNode(Scene* scene);
~ExplosionNode() override;
void Draw(FrameDef* frame_def) override;
void Step() override;
auto position() const -> std::vector<float> { return position_; }
void set_position(const std::vector<float>& vals);
auto velocity() const -> std::vector<float> { return velocity_; }
void set_velocity(const std::vector<float>& vals);
auto radius() const -> float { return radius_; }
void set_radius(float val) { radius_ = val; }
auto color() const -> std::vector<float> { return color_; }
void set_color(const std::vector<float>& vals);
auto big() const -> bool { return big_; }
void set_big(bool val);
private:
millisecs_t birth_time_;
bool check_draw_distortion_{true};
bool big_{};
bool draw_distortion_{};
bool have_distortion_lock_{};
float radius_{1.0f};
std::vector<float> position_{0.0f, 0.0f, 0.0f};
std::vector<float> velocity_{0.0f, 0.0f, 0.0f};
std::vector<float> color_{0.9f, 0.3f, 0.1f};
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_EXPLOSION_NODE_H_

View File

@ -0,0 +1,691 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/flag_node.h"
#include <algorithm>
#include "ballistica/dynamics/bg/bg_dynamics_shadow.h"
#include "ballistica/generic/utils.h"
#include "ballistica/graphics/area_of_interest.h"
#include "ballistica/graphics/camera.h"
#include "ballistica/graphics/component/object_component.h"
#include "ballistica/graphics/component/simple_component.h"
#include "ballistica/graphics/graphics_server.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
const int kFlagSizeX{5};
const int kFlagSizeY{5};
const float kFlagCanvasWidth{1.0f};
const float kFlagCanvasHeight{1.0f};
const float kFlagCanvasScaleX{kFlagCanvasWidth / kFlagSizeX};
const float kFlagCanvasScaleY{kFlagCanvasHeight / kFlagSizeY};
// NOLINTNEXTLINE(cert-err58-cpp)
const float kFlagCanvasScaleDiagonal{
sqrtf(kFlagCanvasScaleX * kFlagCanvasScaleX
+ kFlagCanvasScaleY * kFlagCanvasScaleY)};
const float kFlagRadius{0.1f};
const float kFlagHeight{1.5f};
const float kFlagMassRadius{0.3f};
const float kFlagMassHeight{1.0f};
const float kFlagDensity{1.0f};
const float kStiffness{0.4f};
const float kWindStrength{0.002f};
const float kGravityStrength{0.0012f};
const float kDampingStrength{0.0f};
const float kDragStrength{0.1f};
#if !BA_HEADLESS_BUILD
class FlagNode::FullShadowSet : public Object {
public:
BGDynamicsShadow shadow_pole_bottom_;
BGDynamicsShadow shadow_pole_middle_;
BGDynamicsShadow shadow_pole_top_;
BGDynamicsShadow shadow_flag_;
};
class FlagNode::SimpleShadowSet : public Object {
public:
BGDynamicsShadow shadow_;
};
#endif // !BA_HEADLESS_BUILD
class FlagNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS FlagNode
BA_NODE_CREATE_CALL(CreateFlag);
BA_BOOL_ATTR(is_area_of_interest, is_area_of_interest, SetIsAreaOfInterest);
BA_FLOAT_ARRAY_ATTR(position, getPosition, SetPosition);
BA_TEXTURE_ATTR(color_texture, color_texture, set_color_texture);
BA_BOOL_ATTR(lightWeight, light_weight, SetLightWeight);
BA_FLOAT_ARRAY_ATTR(color, color, SetColor);
BA_MATERIAL_ARRAY_ATTR(materials, GetMaterials, SetMaterials);
#undef BA_NODE_TYPE_CLASS
FlagNodeType()
: NodeType("flag", CreateFlag),
is_area_of_interest(this),
position(this),
color_texture(this),
lightWeight(this),
color(this),
materials(this) {}
};
static NodeType* node_type{};
auto FlagNode::InitType() -> NodeType* {
node_type = new FlagNodeType();
return node_type;
}
enum FlagBodyType { kPoleBodyID };
FlagNode::FlagNode(Scene* scene) : Node(scene, node_type), part_(this) {
body_ = Object::New<RigidBody>(
kPoleBodyID, &part_, RigidBody::Type::kBody, RigidBody::Shape::kCapsule,
RigidBody::kCollideActive, RigidBody::kCollideAll);
UpdateDimensions();
dBodySetPosition(body_->body(), 0, 1.2f, 0);
dQuaternion iq;
dQFromAxisAndAngle(iq, 1, 0, 0, -90.0f * (kPi / 180.0f));
dBodySetQuaternion(body_->body(), iq);
ResetFlagMesh();
// Set our mesh static data and indices once.
auto indices(
Object::New<MeshIndexBuffer16>(6 * (kFlagSizeX - 1) * (kFlagSizeY - 1)));
uint16_t* index = &indices->elements[0];
auto v_static(Object::New<MeshBuffer<VertexObjectSplitStatic>>(kFlagSizeX
* kFlagSizeY));
VertexObjectSplitStatic* vs = &v_static->elements[0];
int x_inc = 65535 / (kFlagSizeX - 1);
int y_inc = 65535 / (kFlagSizeY - 1);
for (int y = 0; y < kFlagSizeY - 1; y++) {
for (int x = 0; x < kFlagSizeX - 1; x++) {
*index++ = static_cast_check_fit<uint16_t>(kFlagSizeX * y + x);
*index++ = static_cast_check_fit<uint16_t>(kFlagSizeX * y + x + 1);
*index++ = static_cast_check_fit<uint16_t>(kFlagSizeX * (y + 1) + x);
*index++ = static_cast_check_fit<uint16_t>(kFlagSizeX * (y + 1) + x);
*index++ = static_cast_check_fit<uint16_t>(kFlagSizeX * y + x + 1);
*index++ = static_cast_check_fit<uint16_t>(kFlagSizeX * (y + 1) + x + 1);
}
}
for (int y = 0; y < kFlagSizeY; y++) {
for (int x = 0; x < kFlagSizeX; x++) {
vs[kFlagSizeX * y + x].uv[0] = static_cast_check_fit<uint16_t>(x_inc * x);
vs[kFlagSizeX * y + x].uv[1] = static_cast_check_fit<uint16_t>(y_inc * y);
}
}
mesh_.SetIndexData(indices);
mesh_.SetStaticData(v_static);
// Create our shadow set.
UpdateForGraphicsQuality(g_graphics_server->quality());
}
auto FlagNode::getPosition() const -> std::vector<float> {
const dReal* p = dGeomGetPosition(body_->geom());
std::vector<float> f(3);
f[0] = p[0];
f[1] = p[1];
f[2] = p[2];
return f;
}
void FlagNode::SetColor(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for color");
}
color_ = vals;
}
void FlagNode::SetLightWeight(bool val) {
light_weight_ = val;
UpdateDimensions();
}
void FlagNode::SetPosition(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for position");
}
dQuaternion iq;
dQFromAxisAndAngle(iq, 1, 0, 0, -90 * (kPi / 180.0f));
dBodySetPosition(body_->body(), vals[0], vals[1], vals[2]);
dBodySetQuaternion(body_->body(), iq);
dBodySetLinearVel(body_->body(), 0, 0, 0);
dBodySetAngularVel(body_->body(), 0, 0, 0);
ResetFlagMesh();
}
void FlagNode::SetIsAreaOfInterest(bool val) {
if ((val && area_of_interest_ == nullptr)
|| (!val && area_of_interest_ != nullptr)) {
// Either make one or kill the one we had.
if (val) {
assert(area_of_interest_ == nullptr);
area_of_interest_ = g_graphics->camera()->NewAreaOfInterest(false);
} else {
assert(area_of_interest_ != nullptr);
g_graphics->camera()->DeleteAreaOfInterest(area_of_interest_);
area_of_interest_ = nullptr;
}
}
}
auto FlagNode::GetMaterials() const -> std::vector<Material*> {
return part_.GetMaterials();
}
void FlagNode::SetMaterials(const std::vector<Material*>& vals) {
part_.SetMaterials(vals);
}
void FlagNode::UpdateDimensions() {
float density_scale =
(g_graphics->camera()->happy_thoughts_mode()) ? 0.3f : 1.0f;
body_->SetDimensions(kFlagRadius, kFlagHeight - 2 * kFlagRadius, 0,
kFlagMassRadius, kFlagMassHeight, 0.0f,
kFlagDensity * density_scale);
}
FlagNode::~FlagNode() {
if (area_of_interest_)
g_graphics->camera()->DeleteAreaOfInterest(area_of_interest_);
}
void FlagNode::HandleMessage(const char* data_in) {
const char* data = data_in;
bool handled = true;
switch (extract_node_message_type(&data)) {
case NodeMessageType::kFooting: {
footing_ += Utils::ExtractInt8(&data);
break;
}
case NodeMessageType::kImpulse: {
float px = Utils::ExtractFloat16NBO(&data);
float py = Utils::ExtractFloat16NBO(&data);
float pz = Utils::ExtractFloat16NBO(&data);
float vx = Utils::ExtractFloat16NBO(&data);
float vy = Utils::ExtractFloat16NBO(&data);
float vz = Utils::ExtractFloat16NBO(&data);
float mag = Utils::ExtractFloat16NBO(&data);
float velocity_mag = Utils::ExtractFloat16NBO(&data);
float radius = Utils::ExtractFloat16NBO(&data);
Utils::ExtractInt16NBO(&data); // calc-force-only
float force_dir_x = Utils::ExtractFloat16NBO(&data);
float force_dir_y = Utils::ExtractFloat16NBO(&data);
float force_dir_z = Utils::ExtractFloat16NBO(&data);
float applied_mag = body_->ApplyImpulse(
px, py, pz, vx, vy, vz, force_dir_x, force_dir_y, force_dir_z,
0.2f * mag, 0.2f * velocity_mag, radius, false);
Vector3f to_flag =
Vector3f(px, py, pz) - Vector3f(dBodyGetPosition(body_->body()));
to_flag *= -0.0001f * applied_mag / to_flag.Length();
flag_impulse_add_x_ += to_flag.x;
flag_impulse_add_y_ += to_flag.y;
flag_impulse_add_z_ += to_flag.z;
have_flag_impulse_ = true;
break;
}
default:
handled = false;
break;
}
if (!handled) Node::HandleMessage(data_in);
}
void FlagNode::Draw(FrameDef* frame_def) {
#if !BA_HEADLESS_BUILD
// Flag cloth.
{
// Update the dynamic portion of our mesh data.
// FIXME - should move this all to BG dynamics thread
auto v_dynamic(Object::New<MeshBuffer<VertexObjectSplitDynamic>>(25));
VertexObjectSplitDynamic* vd = &v_dynamic->elements[0];
for (int i = 0; i < 25; i++) {
vd[i].position[0] = flag_points_[i].x;
vd[i].position[1] = flag_points_[i].y;
vd[i].position[2] = flag_points_[i].z;
vd[i].normal[0] = static_cast_check_fit<int16_t>(std::max(
-32767,
std::min(32767, static_cast<int>(flag_normals_[i].x * 32767.0f))));
vd[i].normal[1] = static_cast_check_fit<int16_t>(std::max(
-32767,
std::min(32767, static_cast<int>(flag_normals_[i].y * 32767.0f))));
vd[i].normal[2] = static_cast_check_fit<int16_t>(std::max(
-32767,
std::min(32767, static_cast<int>(flag_normals_[i].z * 32767.0f))));
}
mesh_.SetDynamicData(v_dynamic);
// Render a subtle sharp shadow in higher quality modes.
if (frame_def->quality() > GraphicsQuality::kLow) {
SimpleComponent c(frame_def->light_shadow_pass());
c.SetTransparent(true);
c.SetColor(color_[0] * 0.1f, color_[1] * 0.1f, color_[2] * 0.1f, 0.02f);
c.SetDoubleSided(true);
c.DrawMesh(&mesh_);
c.Submit();
}
// Now beauty pass.
{
ObjectComponent c(frame_def->beauty_pass());
c.SetWorldSpace(true);
c.SetColor(color_[0], color_[1], color_[2]);
c.SetReflection(ReflectionType::kSoft);
c.SetReflectionScale(0.05f, 0.05f, 0.05f);
c.SetDoubleSided(true);
c.SetTexture(color_texture_);
c.DrawMesh(&mesh_);
c.Submit();
}
float s_scale, s_density;
SimpleComponent c(frame_def->light_shadow_pass());
c.SetTexture(g_media->GetTexture(SystemTextureID::kShadow));
c.SetTransparent(true);
FullShadowSet* full_shadows = full_shadow_set_.get();
if (full_shadows) {
// Pole bottom.
{
full_shadows->shadow_pole_bottom_.GetValues(&s_scale, &s_density);
const Vector3f& p(full_shadows->shadow_pole_bottom_.GetPosition());
g_graphics->DrawBlotch(p, 0.4f * s_scale, 0, 0, 0, s_density * 0.25f);
}
// Pole middle.
{
full_shadows->shadow_pole_middle_.GetValues(&s_scale, &s_density);
const Vector3f& p(full_shadows->shadow_pole_middle_.GetPosition());
g_graphics->DrawBlotch(p, 0.4f * s_scale, 0, 0, 0, s_density * 0.25f);
}
// Pole top.
{
full_shadows->shadow_pole_middle_.GetValues(&s_scale, &s_density);
const Vector3f& p(full_shadows->shadow_pole_top_.GetPosition());
g_graphics->DrawBlotch(p, 0.4f * s_scale, 0, 0, 0, s_density * 0.25f);
}
// Flag center.
{
full_shadows->shadow_flag_.GetValues(&s_scale, &s_density);
const Vector3f& p(full_shadows->shadow_flag_.GetPosition());
g_graphics->DrawBlotch(p, 0.8f * s_scale, 0, 0, 0, s_density * 0.3f);
}
} else {
SimpleShadowSet* simple_shadows = simple_shadow_set_.get();
assert(simple_shadows);
simple_shadows->shadow_.GetValues(&s_scale, &s_density);
const Vector3f& p(simple_shadows->shadow_.GetPosition());
g_graphics->DrawBlotch(p, 0.8f * s_scale, 0, 0, 0, s_density * 0.5f);
}
c.Submit();
}
// Flag pole.
{
ObjectComponent c(frame_def->beauty_pass());
c.SetTexture(g_media->GetTexture(SystemTextureID::kFlagPole));
c.SetReflection(ReflectionType::kSharp);
c.SetReflectionScale(0.1f, 0.1f, 0.1f);
c.PushTransform();
c.TransformToBody(*body_);
c.DrawModel(g_media->GetModel(SystemModelID::kFlagPole));
c.PopTransform();
c.Submit();
}
#endif // !BA_HEADLESS_BUILD
}
void FlagNode::UpdateAreaOfInterest() {
AreaOfInterest* aoi = area_of_interest_;
if (!aoi) return;
assert(body_.exists());
aoi->set_position(Vector3f(dGeomGetPosition(body_->geom())));
aoi->SetRadius(5.0f);
}
void FlagNode::Step() {
// On happy thoughts, keep us on the 2d plane.
if (g_graphics->camera()->happy_thoughts_mode() && body_.exists()) {
dBodyID b;
const dReal *p, *v;
b = body_->body();
p = dBodyGetPosition(b);
float smoothing = 0.98f;
dBodySetPosition(
b, p[0], p[1],
p[2] * smoothing + (1.0f - smoothing) * kHappyThoughtsZPlane);
v = dBodyGetLinearVel(b);
dBodySetLinearVel(b, v[0], v[1], v[2] * smoothing);
}
// update our area-of-interest if we have one
UpdateAreaOfInterest();
// FIXME: This should probably happen for RBDs automatically?
body_->UpdateBlending();
// Update our shadow objects.
dBodyID b = body_->body();
assert(b);
#if !BA_HEADLESS_BUILD
dVector3 p;
FullShadowSet* full_shadows = full_shadow_set_.get();
if (full_shadows) {
full_shadows->shadow_flag_.SetPosition(
flag_points_[kFlagSizeX * (kFlagSizeY / 2) + (kFlagSizeX / 2)]);
dBodyGetRelPointPos(b, 0, 0, kFlagHeight * -0.4f, p);
full_shadows->shadow_pole_bottom_.SetPosition(Vector3f(p));
full_shadows->shadow_pole_middle_.SetPosition(
Vector3f(dBodyGetPosition(b)));
dBodyGetRelPointPos(b, 0, 0, kFlagHeight * 0.4f, p);
full_shadows->shadow_pole_top_.SetPosition(Vector3f(p));
} else {
SimpleShadowSet* simple_shadows = simple_shadow_set_.get();
assert(simple_shadows);
dBodyGetRelPointPos(b, 0, 0, kFlagHeight * -0.3f, p);
simple_shadows->shadow_.SetPosition(Vector3f(p));
}
#endif // !BA_HEADLESS_BUILD
if (dBodyIsEnabled(body_->body())) {
// Try to keep upright by pushing the top of the
// flag to be above the bottom.
{
float force_mag = 40.0f;
float force_max = 40.0f;
float min_dist = 0.05f;
if (light_weight_) {
force_mag *= 0.3f;
force_max *= 0.3f;
}
dVector3 bottom, top;
dBodyGetRelPointPos(body_->body(), 0, 0, kFlagHeight / 2.0f, top);
dBodyGetRelPointPos(body_->body(), 0, 0, -kFlagHeight / 2.0f, bottom);
Vector3f top_v(top[0], top[1], top[2]);
Vector3f bot_v(bottom[0], bottom[1], bottom[2]);
Vector3f target_v(bot_v.x, bot_v.y + kFlagHeight, bot_v.z);
if ((std::abs(target_v.x - top_v.x) > min_dist)
|| (std::abs(target_v.y - top_v.y) > min_dist)
|| (std::abs(target_v.z - top_v.z) > min_dist)) {
dBodyEnable(body_->body());
Vector3f fV((target_v - top_v) * force_mag);
float mag = fV.Length();
if (mag > force_max) fV *= force_max / mag;
dBodyAddForceAtPos(body_->body(), fV.x, fV.y, fV.z, top_v.x, top_v.y,
top_v.z);
dBodyAddForceAtPos(body_->body(), -fV.x, -fV.y, -fV.z, bot_v.x, bot_v.y,
bot_v.z);
}
}
// Apply damping force.
float linear_damping_x = 1.0f;
float linear_damping_y = 1.0f;
float linear_damping_z = 1.0f;
float rotational_damping_x = 1.0f;
float rotational_damping_y = 1.0f;
float rotational_damping_z = 1.0f;
if (light_weight_) {
linear_damping_x *= 0.3f;
linear_damping_y *= 0.3f;
linear_damping_z *= 0.3f;
rotational_damping_x *= 0.3f;
rotational_damping_y *= 0.3f;
rotational_damping_z *= 0.3f;
}
// Don't add forces if we're asleep otherwise we'll explode when we wake up.
dMass mass;
dBodyGetMass(body_->body(), &mass);
const dReal* vel;
dReal force[3];
vel = dBodyGetAngularVel(body_->body());
force[0] = -1 * mass.mass * vel[0] * rotational_damping_x;
force[1] = -1 * mass.mass * vel[1] * rotational_damping_y;
force[2] = -1 * mass.mass * vel[2] * rotational_damping_z;
dBodyAddTorque(body_->body(), force[0], force[1], force[2]);
vel = dBodyGetLinearVel(body_->body());
force[0] = -1 * mass.mass * vel[0] * linear_damping_x;
force[1] = -1 * mass.mass * vel[1] * linear_damping_y;
force[2] = -1 * mass.mass * vel[2] * linear_damping_z;
dBodyAddForce(body_->body(), force[0], force[1], force[2]);
// If we're out of bounds, arrange to have ourself informed.
{
const dReal* p2 = dBodyGetPosition(body_->body());
if (scene()->IsOutOfBounds(p2[0], p2[1], p2[2])) {
scene()->AddOutOfBoundsNode(this);
}
}
}
UpdateFlagMesh();
}
auto FlagNode::GetRigidBody(int id) -> RigidBody* { return body_.get(); }
static auto FlagPointIndex(int x, int y) -> int {
return kFlagSizeX * (y) + (x);
}
void FlagNode::UpdateSpringPoint(int p1, int p2, float rest_length) {
Vector3f d = flag_points_[p2] - flag_points_[p1];
float mag = d.Length();
if (mag > (rest_length + 0.05f)) {
mag = rest_length + 0.05f;
}
Vector3f f = d / mag * kStiffness * (mag - rest_length);
flag_velocities_[p1] += f;
flag_velocities_[p2] -= f;
Vector3f vd =
kDampingStrength * (flag_velocities_[p1] - flag_velocities_[p2]);
flag_velocities_[p1] -= vd;
flag_velocities_[p2] += vd;
}
void FlagNode::ResetFlagMesh() {
dVector3 up, side, top;
dBodyGetRelPointPos(body_->body(), 0, 0, kFlagHeight / 2, top);
dBodyVectorToWorld(body_->body(), 0, 0, 1, up);
dBodyVectorToWorld(body_->body(), 1, 0, 0, side);
Vector3f up_v(up);
Vector3f side_v(side);
Vector3f top_v(top);
up_v *= kFlagCanvasScaleY;
side_v *= kFlagCanvasScaleX;
for (int y = 0; y < kFlagSizeY; y++) {
for (int x = 0; x < kFlagSizeX; x++) {
int i = kFlagSizeX * y + x;
Vector3f p =
top_v - up_v * static_cast<float>(y) + side_v * static_cast<float>(x);
flag_points_[i].x = p.x;
flag_points_[i].y = p.y;
flag_points_[i].z = p.z;
flag_velocities_[i] = kVector3f0;
}
}
flag_impulse_add_x_ = flag_impulse_add_y_ = flag_impulse_add_z_ = 0;
have_flag_impulse_ = false;
}
void FlagNode::UpdateFlagMesh() {
dVector3 up, top;
dBodyGetRelPointPos(body_->body(), 0, 0, kFlagHeight / 2, top);
dBodyVectorToWorld(body_->body(), 0, 0, 1, up);
Vector3f up_v(up);
Vector3f top_v(top);
up_v *= kFlagCanvasScaleY;
// Move our attachment points into place.
for (int y = 0; y < kFlagSizeY; y++) {
int i = kFlagSizeX * y;
Vector3f p = top_v - up_v * static_cast<float>(y);
flag_points_[i].x = p.x;
flag_points_[i].y = p.y;
flag_points_[i].z = p.z;
flag_velocities_[i] = kVector3f0;
}
// Push our flag points around.
const dReal* flag_vel = dBodyGetLinearVel(body_->body());
Vector3f wind_vec = {0.0f, 0.0f, 0.0f};
bool do_wind = true;
if (RandomFloat() > 0.85f) {
wind_rand_x_ = 0.5f - RandomFloat();
wind_rand_y_ = 0.5f - RandomFloat();
if (scene()->stepnum() % 100 > 50) {
wind_rand_z_ = RandomFloat();
} else {
wind_rand_z_ = -RandomFloat();
}
wind_rand_ = static_cast<int>(scene()->stepnum());
}
if (explicit_bool(do_wind)) {
wind_vec = -2.0f * Vector3f(flag_vel[0], flag_vel[1], flag_vel[2]);
// If the flag is moving less than 1.0, add some ambient wind.
if (wind_vec.LengthSquared() < 1.0f) {
wind_vec += (1.0f - wind_vec.LengthSquared()) * Vector3f(5, 0, 0);
}
wind_vec +=
3.0f
* Vector3f(0.15f * wind_rand_x_, wind_rand_y_, 1.5f * wind_rand_z_);
}
for (int y = 0; y < kFlagSizeY - 1; y++) {
for (int x = 0; x < kFlagSizeX - 1; x++) {
int top_left, top_right, bot_left, bot_right;
top_left = FlagPointIndex(x, y);
top_right = FlagPointIndex(x + 1, y);
bot_left = FlagPointIndex(x, y + 1);
bot_right = FlagPointIndex(x + 1, y + 1);
flag_velocities_[top_left].y -= kGravityStrength;
flag_velocities_[top_right].y -= kGravityStrength;
flag_velocities_[top_right].x *= (1.0f - kDragStrength);
flag_velocities_[top_right].y *= (1.0f - kDragStrength);
flag_velocities_[top_right].z *= (1.0f - kDragStrength);
if (have_flag_impulse_) {
flag_velocities_[top_left].x += flag_impulse_add_x_;
flag_velocities_[top_left].y += flag_impulse_add_y_;
flag_velocities_[top_left].z += flag_impulse_add_z_;
flag_velocities_[top_right].x += flag_impulse_add_x_;
flag_velocities_[top_right].y += flag_impulse_add_y_;
flag_velocities_[top_right].z += flag_impulse_add_z_;
}
// Wind.
// FIXME - we can prolly move some of this out of the inner loop..
if (explicit_bool(do_wind)) {
flag_velocities_[top_right].x +=
wind_vec.x * kWindStrength
* (Utils::precalc_rands_1[wind_rand_ % kPrecalcRandsCount] - 0.3f);
flag_velocities_[top_right].y +=
wind_vec.y * kWindStrength
* (Utils::precalc_rands_2[wind_rand_ % kPrecalcRandsCount] - 0.3f);
flag_velocities_[top_right].z +=
wind_vec.z * kWindStrength
* (Utils::precalc_rands_3[wind_rand_ % kPrecalcRandsCount] - 0.3f);
}
UpdateSpringPoint(top_left, top_right, kFlagCanvasScaleX);
UpdateSpringPoint(bot_left, bot_right, kFlagCanvasScaleX);
UpdateSpringPoint(top_left, bot_left, kFlagCanvasScaleY);
UpdateSpringPoint(top_right, bot_right, kFlagCanvasScaleY);
UpdateSpringPoint(top_left, bot_right, kFlagCanvasScaleDiagonal);
UpdateSpringPoint(top_right, bot_left, kFlagCanvasScaleDiagonal);
}
}
flag_impulse_add_x_ = flag_impulse_add_y_ = flag_impulse_add_z_ = 0;
// Now update positions (except pole points).
for (int y = 0; y < kFlagSizeY; y++) {
for (int x = 0; x < kFlagSizeX; x++) {
int i = kFlagSizeX * y + x;
flag_points_[i] += flag_velocities_[i];
}
}
// Now calc normals.
for (int y = 0; y < kFlagSizeY; y++) {
for (int x = 0; x < kFlagSizeX; x++) {
// Calc the normal for this vert.
int xclamped = std::min(x, kFlagSizeX - 2);
int yclamped = std::min(y, kFlagSizeY - 2);
int i = kFlagSizeX * yclamped + xclamped;
flag_normals_[i] =
Vector3f::Cross(flag_points_[i + 1] - flag_points_[i],
flag_points_[i + kFlagSizeX] - flag_points_[i])
.Normalized();
}
}
}
void FlagNode::GetRigidBodyPickupLocations(int id, float* obj, float* character,
float* hand1, float* hand2) {
obj[0] = 0;
obj[1] = 0;
obj[2] = -0.6f;
character[0] = 0;
character[1] = -0.4f;
character[2] = 0.3f;
hand1[0] = hand1[1] = hand1[2] = 0;
hand2[0] = hand2[1] = hand2[2] = 0;
hand2[0] = 0.05f;
hand2[2] = -0.1f;
hand1[0] = -0.05f;
hand1[2] = -0.05f;
}
void FlagNode::OnGraphicsQualityChanged(GraphicsQuality q) {
UpdateForGraphicsQuality(q);
}
void FlagNode::UpdateForGraphicsQuality(GraphicsQuality quality) {
#if !BA_HEADLESS_BUILD
if (quality >= GraphicsQuality::kMedium) {
full_shadow_set_ = Object::New<FullShadowSet>();
simple_shadow_set_.Clear();
} else {
simple_shadow_set_ = Object::New<SimpleShadowSet>();
full_shadow_set_.Clear();
}
#endif
}
} // namespace ballistica

View File

@ -0,0 +1,77 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_FLAG_NODE_H_
#define BALLISTICA_SCENE_NODE_FLAG_NODE_H_
#include <vector>
#include "ballistica/dynamics/part.h"
#include "ballistica/graphics/renderer.h"
#include "ballistica/scene/node/node.h"
namespace ballistica {
class FlagNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit FlagNode(Scene* scene);
~FlagNode() override;
void HandleMessage(const char* data) override;
void Draw(FrameDef* frame_def) override;
void Step() override;
auto GetRigidBody(int id) -> RigidBody* override;
auto is_area_of_interest() const -> bool {
return (area_of_interest_ != nullptr);
}
void SetIsAreaOfInterest(bool val);
auto getPosition() const -> std::vector<float>;
void SetPosition(const std::vector<float>& vals);
auto color_texture() const -> Texture* { return color_texture_.get(); }
void set_color_texture(Texture* val) { color_texture_ = val; }
auto light_weight() const -> bool { return light_weight_; }
void SetLightWeight(bool val);
auto color() const -> std::vector<float> { return color_; }
void SetColor(const std::vector<float>& vals);
auto GetMaterials() const -> std::vector<Material*>;
void SetMaterials(const std::vector<Material*>& materials);
private:
class FullShadowSet;
class SimpleShadowSet;
void UpdateAreaOfInterest();
void GetRigidBodyPickupLocations(int id, float* obj, float* character,
float* hand1, float* hand2) override;
void UpdateDimensions();
void ResetFlagMesh();
void UpdateFlagMesh();
void OnGraphicsQualityChanged(GraphicsQuality q) override;
void UpdateForGraphicsQuality(GraphicsQuality q);
void UpdateSpringPoint(int p1, int p2, float rest_length);
AreaOfInterest* area_of_interest_ = nullptr;
Part part_;
std::vector<float> color_ = {1.0f, 1.0f, 1.0f};
Object::Ref<RigidBody> body_{nullptr};
Object::Ref<Texture> color_texture_;
MeshIndexedObjectSplit mesh_;
#if !BA_HEADLESS_BUILD
Object::Ref<FullShadowSet> full_shadow_set_;
Object::Ref<SimpleShadowSet> simple_shadow_set_;
#endif // !BA_HEADLESS_BUILD
int wind_rand_{};
float wind_rand_x_{};
float wind_rand_y_{};
float wind_rand_z_{};
float flag_impulse_add_x_{};
float flag_impulse_add_y_{};
float flag_impulse_add_z_{};
bool have_flag_impulse_{};
int footing_{};
bool light_weight_{};
Vector3f flag_points_[25]{};
Vector3f flag_normals_[25]{};
Vector3f flag_velocities_[25]{};
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_FLAG_NODE_H_

View File

@ -0,0 +1,60 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/flash_node.h"
#include <vector>
#include "ballistica/graphics/component/object_component.h"
#include "ballistica/graphics/renderer.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
class FlashNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS FlashNode
BA_NODE_CREATE_CALL(CreateFlash);
BA_FLOAT_ARRAY_ATTR(position, position, SetPosition);
BA_FLOAT_ATTR(size, size, set_size);
BA_FLOAT_ARRAY_ATTR(color, color, set_color);
#undef BA_NODE_TYPE_CLASS
FlashNodeType()
: NodeType("flash", CreateFlash),
position(this),
size(this),
color(this) {}
};
static NodeType* node_type{};
auto FlashNode::InitType() -> NodeType* {
node_type = new FlashNodeType();
return node_type;
}
FlashNode::FlashNode(Scene* scene) : Node(scene, node_type) {}
FlashNode::~FlashNode() = default;
void FlashNode::SetPosition(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of size 3 for position");
}
position_ = vals;
}
void FlashNode::Draw(FrameDef* frame_def) {
ObjectComponent c(frame_def->beauty_pass());
c.SetLightShadow(LightShadowType::kNone);
c.SetColor(color_[0], color_[1], color_[2], 1.0f);
c.PushTransform();
c.Translate(position_[0], position_[1], position_[2]);
c.Scale(size_, size_, size_);
c.Rotate(RandomFloat() * 360.0f, 1, 1, 0);
c.DrawModel(g_media->GetModel(SystemModelID::kFlash));
c.PopTransform();
c.Submit();
}
} // namespace ballistica

View File

@ -0,0 +1,33 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_FLASH_NODE_H_
#define BALLISTICA_SCENE_NODE_FLASH_NODE_H_
#include <vector>
#include "ballistica/scene/node/node.h"
namespace ballistica {
class FlashNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit FlashNode(Scene* scene);
~FlashNode() override;
void Draw(FrameDef* frame_def) override;
auto position() const -> std::vector<float> { return position_; }
void SetPosition(const std::vector<float>& vals);
auto size() const -> float { return size_; }
void set_size(float val) { size_ = val; }
auto color() const -> std::vector<float> { return color_; }
void set_color(const std::vector<float>& vals) { color_ = vals; }
private:
std::vector<float> position_ = {0.0f, 0.0f, 0.0f};
float size_ = 1.0f;
std::vector<float> color_ = {0.5f, 0.5f, 0.5f};
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_FLASH_NODE_H_

View File

@ -0,0 +1,444 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/globals_node.h"
#include "ballistica/audio/audio.h"
#include "ballistica/dynamics/bg/bg_dynamics.h"
#include "ballistica/game/host_activity.h"
#include "ballistica/graphics/camera.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/graphics/vr_graphics.h"
#include "ballistica/python/python.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
class GlobalsNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS GlobalsNode
BA_NODE_CREATE_CALL(CreateGlobals);
BA_INT64_ATTR_READONLY(real_time, GetRealTime);
BA_INT64_ATTR_READONLY(time, GetTime);
BA_INT64_ATTR_READONLY(step, GetStep);
BA_FLOAT_ATTR(debris_friction, debris_friction, SetDebrisFriction);
BA_BOOL_ATTR(floor_reflection, floor_reflection, SetFloorReflection);
BA_FLOAT_ATTR(debris_kill_height, debris_kill_height, SetDebrisKillHeight);
BA_STRING_ATTR(camera_mode, GetCameraMode, SetCameraMode);
BA_BOOL_ATTR(happy_thoughts_mode, happy_thoughts_mode, SetHappyThoughtsMode);
BA_FLOAT_ARRAY_ATTR(shadow_scale, shadow_scale, SetShadowScale);
BA_FLOAT_ARRAY_ATTR(area_of_interest_bounds, area_of_interest_bounds,
set_area_of_interest_bounds);
BA_FLOAT_ARRAY_ATTR(shadow_range, shadow_range, SetShadowRange);
BA_FLOAT_ARRAY_ATTR(shadow_offset, shadow_offset, SetShadowOffset);
BA_BOOL_ATTR(shadow_ortho, shadow_ortho, SetShadowOrtho);
BA_FLOAT_ARRAY_ATTR(tint, tint, SetTint);
BA_FLOAT_ARRAY_ATTR(vr_overlay_center, vr_overlay_center, SetVROverlayCenter);
BA_BOOL_ATTR(vr_overlay_center_enabled, vr_overlay_center_enabled,
SetVROverlayCenterEnabled);
BA_FLOAT_ARRAY_ATTR(ambient_color, ambient_color, SetAmbientColor);
BA_FLOAT_ARRAY_ATTR(vignette_outer, vignette_outer, SetVignetteOuter);
BA_FLOAT_ARRAY_ATTR(vignette_inner, vignette_inner, SetVignetteInner);
BA_BOOL_ATTR(allow_kick_idle_players, allow_kick_idle_players,
SetAllowKickIdlePlayers);
BA_BOOL_ATTR(slow_motion, slow_motion, SetSlowMotion);
BA_BOOL_ATTR(paused, paused, SetPaused);
BA_FLOAT_ARRAY_ATTR(vr_camera_offset, vr_camera_offset, SetVRCameraOffset);
BA_BOOL_ATTR(use_fixed_vr_overlay, use_fixed_vr_overlay,
SetUseFixedVROverlay);
BA_FLOAT_ATTR(vr_near_clip, vr_near_clip, SetVRNearClip);
BA_BOOL_ATTR(music_continuous, music_continuous, set_music_continuous);
BA_STRING_ATTR(music, music, set_music);
BA_INT_ATTR(music_count, music_count, SetMusicCount);
#undef BA_NODE_TYPE_CLASS
GlobalsNodeType()
: NodeType("globals", CreateGlobals),
real_time(this),
time(this),
step(this),
debris_friction(this),
floor_reflection(this),
debris_kill_height(this),
camera_mode(this),
happy_thoughts_mode(this),
shadow_scale(this),
area_of_interest_bounds(this),
shadow_range(this),
shadow_offset(this),
shadow_ortho(this),
tint(this),
vr_overlay_center(this),
vr_overlay_center_enabled(this),
ambient_color(this),
vignette_outer(this),
vignette_inner(this),
allow_kick_idle_players(this),
slow_motion(this),
paused(this),
vr_camera_offset(this),
use_fixed_vr_overlay(this),
vr_near_clip(this),
music_continuous(this),
music(this),
music_count(this) {}
};
static NodeType* node_type{};
auto GlobalsNode::InitType() -> NodeType* {
node_type = new GlobalsNodeType();
return node_type;
}
GlobalsNode::GlobalsNode(Scene* scene) : Node(scene, node_type) {
// Set ourself as the current globals node for our scene.
this->scene()->set_globals_node(this);
// If we're being made in a host-activity, also set ourself as the current
// globals node for our activity. (there should only be one, so complain if
// there already is one).
// FIXME: Need to update this for non-host activities at some point.
if (HostActivity* ha = context().GetHostActivity()) {
if (ha->globals_node()) {
Log("WARNING: more than one globals node created in HostActivity; this "
"shouldn't happen");
}
ha->globals_node_ = this;
// Set some values we always drive even when not the singleton 'current'
// globals (stuff that only affects our activity/scene).
ha->SetGameSpeed(slow_motion_ ? 0.32f : 1.0f);
ha->SetPaused(paused_);
ha->set_allow_kick_idle_players(allow_kick_idle_players_);
this->scene()->set_use_fixed_vr_overlay(use_fixed_vr_overlay_);
}
// If our scene is currently the game's foreground one, go ahead and
// push our values globally.
if (g_game->GetForegroundScene() == this->scene()) {
SetAsForeground();
}
}
GlobalsNode::~GlobalsNode() {
// If we are the current globals node for our scene, clear it out.
if (scene()->globals_node() == this) {
scene()->set_globals_node(nullptr);
}
}
// Called when we're being made the one foreground node and should push our
// values to the global state (since there can be multiple scenes in
// existence, there has to be a single "foreground" globals node in control).
void GlobalsNode::SetAsForeground() {
#if !BA_HEADLESS_BUILD
g_bg_dynamics->SetDebrisFriction(debris_friction_);
g_bg_dynamics->SetDebrisKillHeight(debris_kill_height_);
#endif
g_graphics->set_floor_reflection(floor_reflection_);
g_graphics->camera()->SetMode(camera_mode_);
g_graphics->camera()->set_vr_offset(Vector3f(vr_camera_offset_));
g_graphics->camera()->set_happy_thoughts_mode(happy_thoughts_mode_);
g_graphics->set_shadow_scale(shadow_scale_[0], shadow_scale_[1]);
g_graphics->camera()->set_area_of_interest_bounds(
area_of_interest_bounds_[0], area_of_interest_bounds_[1],
area_of_interest_bounds_[2], area_of_interest_bounds_[3],
area_of_interest_bounds_[4], area_of_interest_bounds_[5]);
g_graphics->SetShadowRange(shadow_range_[0], shadow_range_[1],
shadow_range_[2], shadow_range_[3]);
g_graphics->set_shadow_offset(Vector3f(shadow_offset_));
g_graphics->set_shadow_ortho(shadow_ortho_);
g_graphics->set_tint(Vector3f(tint_));
#if BA_VR_BUILD
if (IsVRMode()) {
auto* vrgraphics = VRGraphics::get();
vrgraphics->set_vr_near_clip(vr_near_clip_);
vrgraphics->set_vr_overlay_center(Vector3f(vr_overlay_center_));
vrgraphics->set_vr_overlay_center_enabled(vr_overlay_center_enabled_);
}
#endif
g_graphics->set_ambient_color(Vector3f(ambient_color_));
g_graphics->set_vignette_outer(Vector3f(vignette_outer_));
g_graphics->set_vignette_inner(Vector3f(vignette_inner_));
g_audio->SetSoundPitch(slow_motion_ ? 0.4f : 1.0f);
// Tell the scripting layer to play our current music.
g_python->PlayMusic(music_, music_continuous_);
}
auto GlobalsNode::IsCurrentGlobals() const -> bool {
// We're current if our scene is the foreground one and we're the globals
// node for our scene.
Scene* scene = this->scene();
assert(scene);
return (g_game->GetForegroundScene() == this->scene()
&& scene->globals_node() == this);
}
auto GlobalsNode::GetRealTime() -> millisecs_t {
// Pull this from our scene so we return consistent values throughout a step.
return scene()->last_step_real_time();
}
auto GlobalsNode::GetTime() -> millisecs_t { return scene()->time(); }
auto GlobalsNode::GetStep() -> int64_t { return scene()->stepnum(); }
void GlobalsNode::SetDebrisFriction(float val) {
debris_friction_ = val;
if (IsCurrentGlobals()) {
#if !BA_HEADLESS_BUILD
g_bg_dynamics->SetDebrisFriction(debris_friction_);
#endif // !BA_HEADLESS_BUILD
}
}
void GlobalsNode::SetVRNearClip(float val) {
vr_near_clip_ = val;
#if BA_VR_BUILD
if (IsVRMode()) {
if (IsCurrentGlobals()) {
VRGraphics::get()->set_vr_near_clip(vr_near_clip_);
}
}
#endif
}
void GlobalsNode::SetFloorReflection(bool val) {
floor_reflection_ = val;
if (IsCurrentGlobals()) {
g_graphics->set_floor_reflection(floor_reflection_);
}
}
void GlobalsNode::SetDebrisKillHeight(float val) {
debris_kill_height_ = val;
if (IsCurrentGlobals()) {
#if !BA_HEADLESS_BUILD
g_bg_dynamics->SetDebrisKillHeight(debris_kill_height_);
#endif // !BA_HEADLESS_BUILD
}
}
void GlobalsNode::SetHappyThoughtsMode(bool val) {
happy_thoughts_mode_ = val;
if (IsCurrentGlobals()) {
g_graphics->camera()->set_happy_thoughts_mode(happy_thoughts_mode_);
}
}
void GlobalsNode::SetShadowScale(const std::vector<float>& vals) {
if (vals.size() != 2) {
throw Exception("Expected float array of length 2 for shadow_scale");
}
shadow_scale_ = vals;
if (IsCurrentGlobals()) {
g_graphics->set_shadow_scale(shadow_scale_[0], shadow_scale_[1]);
}
}
void GlobalsNode::set_area_of_interest_bounds(const std::vector<float>& vals) {
if (vals.size() != 6) {
throw Exception(
"Expected float array of length 6 for area_of_interest_bounds");
}
area_of_interest_bounds_ = vals;
assert(g_graphics->camera());
if (IsCurrentGlobals()) {
g_graphics->camera()->set_area_of_interest_bounds(
area_of_interest_bounds_[0], area_of_interest_bounds_[1],
area_of_interest_bounds_[2], area_of_interest_bounds_[3],
area_of_interest_bounds_[4], area_of_interest_bounds_[5]);
}
}
void GlobalsNode::SetShadowRange(const std::vector<float>& vals) {
if (vals.size() != 4) {
throw Exception("Expected float array of length 4 for shadow_range");
}
shadow_range_ = vals;
if (IsCurrentGlobals()) {
g_graphics->SetShadowRange(shadow_range_[0], shadow_range_[1],
shadow_range_[2], shadow_range_[3]);
}
}
void GlobalsNode::SetShadowOffset(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for shadow_offset");
}
shadow_offset_ = vals;
if (IsCurrentGlobals()) {
g_graphics->set_shadow_offset(Vector3f(shadow_offset_));
}
}
void GlobalsNode::SetVRCameraOffset(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for vr_camera_offset");
}
vr_camera_offset_ = vals;
if (IsCurrentGlobals()) {
g_graphics->camera()->set_vr_offset(Vector3f(vr_camera_offset_));
}
}
void GlobalsNode::SetShadowOrtho(bool val) {
shadow_ortho_ = val;
if (IsCurrentGlobals()) {
g_graphics->set_shadow_ortho(shadow_ortho_);
}
}
void GlobalsNode::SetTint(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for tint");
}
tint_ = vals;
if (IsCurrentGlobals()) {
g_graphics->set_tint(Vector3f(tint_[0], tint_[1], tint_[2]));
}
}
void GlobalsNode::SetVROverlayCenter(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for vr_overlay_center");
}
vr_overlay_center_ = vals;
#if BA_VR_BUILD
if (IsCurrentGlobals()) {
VRGraphics::get()->set_vr_overlay_center(Vector3f(vr_overlay_center_));
}
#endif
}
void GlobalsNode::SetVROverlayCenterEnabled(bool val) {
vr_overlay_center_enabled_ = val;
#if BA_VR_BUILD
if (IsCurrentGlobals()) {
VRGraphics::get()->set_vr_overlay_center_enabled(
vr_overlay_center_enabled_);
}
#endif
}
void GlobalsNode::SetAmbientColor(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for ambient_color");
}
ambient_color_ = vals;
if (IsCurrentGlobals()) {
g_graphics->set_ambient_color(Vector3f(ambient_color_));
}
}
void GlobalsNode::SetVignetteOuter(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for vignette_outer");
}
vignette_outer_ = vals;
if (IsCurrentGlobals()) {
g_graphics->set_vignette_outer(Vector3f(vignette_outer_));
}
}
void GlobalsNode::SetVignetteInner(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of length 3 for vignette_inner");
}
vignette_inner_ = vals;
if (IsCurrentGlobals()) {
g_graphics->set_vignette_inner(Vector3f(vignette_inner_));
}
}
auto GlobalsNode::GetCameraMode() const -> std::string {
switch (camera_mode_) {
case CameraMode::kOrbit:
return "rotate";
case CameraMode::kFollow:
return "follow";
default:
Log("ERROR: Globals: Unrecognized camera_mode_: "
+ std::to_string(static_cast<int>(camera_mode_)));
return "unknown";
}
}
void GlobalsNode::SetCameraMode(const std::string& val) {
if (val == "rotate") {
camera_mode_ = CameraMode::kOrbit;
} else if (val == "follow") {
camera_mode_ = CameraMode::kFollow;
} else {
throw Exception("Invalid camera mode: '" + val
+ R"('; expected "rotate" or "follow")");
}
if (IsCurrentGlobals()) g_graphics->camera()->SetMode(camera_mode_);
}
void GlobalsNode::SetAllowKickIdlePlayers(bool val) {
allow_kick_idle_players_ = val;
// This only means something if we're in a host-activity.
if (HostActivity* ha = context().GetHostActivity()) {
// Set speed on our activity even if we're not the current globals node.
if (ha->globals_node() == this) {
ha->set_allow_kick_idle_players(allow_kick_idle_players_);
}
}
}
void GlobalsNode::SetSlowMotion(bool val) {
slow_motion_ = val;
// This only matters if we're in a host-activity.
// (clients are just driven by whatever steps are in the input-stream)
if (HostActivity* ha = context().GetHostActivity()) {
// Set speed on *our* activity regardless of whether we're the current
// globals node.
if (ha->globals_node() == this) {
ha->SetGameSpeed(slow_motion_ ? 0.32f : 1.0f);
}
}
// Only set pitch if we are the current globals node.
// (FIXME - need to make this per-sound or something)
if (IsCurrentGlobals()) {
g_audio->SetSoundPitch(slow_motion_ ? 0.4f : 1.0f);
}
}
void GlobalsNode::SetPaused(bool val) {
paused_ = val;
// This only matters in a host-activity.
// (clients are just driven by whatever steps are in the input-stream)
if (HostActivity* ha = context().GetHostActivity()) {
// Set speed on our activity even if we're not the current globals node.
if (ha->globals_node() == this) {
ha->SetPaused(paused_);
}
}
}
void GlobalsNode::SetUseFixedVROverlay(bool val) {
use_fixed_vr_overlay_ = val;
// Always apply this value to our scene.
scene()->set_use_fixed_vr_overlay(val);
}
void GlobalsNode::SetMusicCount(int val) {
if (music_count_ != val && IsCurrentGlobals()) {
g_python->PlayMusic(music_, music_continuous_);
}
music_count_ = val;
}
} // namespace ballistica

View File

@ -0,0 +1,130 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_GLOBALS_NODE_H_
#define BALLISTICA_SCENE_NODE_GLOBALS_NODE_H_
#include <string>
#include <vector>
#include "ballistica/scene/node/node.h"
namespace ballistica {
class GlobalsNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit GlobalsNode(Scene* scene);
~GlobalsNode() override;
void SetAsForeground();
auto IsCurrentGlobals() const -> bool;
auto GetRealTime() -> millisecs_t;
auto GetTime() -> millisecs_t;
auto GetStep() -> int64_t;
auto debris_friction() const -> float { return debris_friction_; }
void SetDebrisFriction(float val);
auto floor_reflection() const -> bool { return floor_reflection_; }
void SetFloorReflection(bool val);
auto debris_kill_height() const -> float { return debris_kill_height_; }
void SetDebrisKillHeight(float val);
auto GetCameraMode() const -> std::string;
void SetCameraMode(const std::string& val);
void SetHappyThoughtsMode(bool val);
auto happy_thoughts_mode() const -> bool { return happy_thoughts_mode_; }
auto shadow_scale() const -> const std::vector<float>& {
return shadow_scale_;
}
void SetShadowScale(const std::vector<float>& vals);
auto area_of_interest_bounds() const -> const std::vector<float>& {
return area_of_interest_bounds_;
}
void set_area_of_interest_bounds(const std::vector<float>& vals);
auto shadow_range() const -> const std::vector<float>& {
return shadow_range_;
}
void SetShadowRange(const std::vector<float>& vals);
auto shadow_offset() const -> const std::vector<float>& {
return shadow_offset_;
}
void SetShadowOffset(const std::vector<float>& vals);
auto shadow_ortho() const -> bool { return shadow_ortho_; }
void SetShadowOrtho(bool val);
auto tint() const -> const std::vector<float>& { return tint_; }
void SetTint(const std::vector<float>& vals);
auto vr_overlay_center() const -> const std::vector<float>& {
return vr_overlay_center_;
}
void SetVROverlayCenter(const std::vector<float>& vals);
auto vr_overlay_center_enabled() const -> bool {
return vr_overlay_center_enabled_;
}
void SetVROverlayCenterEnabled(bool);
auto ambient_color() const -> const std::vector<float>& {
return ambient_color_;
}
void SetAmbientColor(const std::vector<float>& vals);
auto vignette_outer() const -> const std::vector<float>& {
return vignette_outer_;
}
void SetVignetteOuter(const std::vector<float>& vals);
auto vignette_inner() const -> const std::vector<float>& {
return vignette_inner_;
}
void SetVignetteInner(const std::vector<float>& vals);
auto allow_kick_idle_players() const -> bool {
return allow_kick_idle_players_;
}
void SetAllowKickIdlePlayers(bool allow);
auto slow_motion() const -> bool { return slow_motion_; }
void SetSlowMotion(bool val);
auto paused() const -> bool { return paused_; }
void SetPaused(bool val);
auto vr_camera_offset() const -> const std::vector<float>& {
return vr_camera_offset_;
}
void SetVRCameraOffset(const std::vector<float>& vals);
auto use_fixed_vr_overlay() const -> bool { return use_fixed_vr_overlay_; }
void SetUseFixedVROverlay(bool val);
auto vr_near_clip() const -> float { return vr_near_clip_; }
void SetVRNearClip(float val);
auto music_continuous() const -> bool { return music_continuous_; }
void set_music_continuous(bool val) { music_continuous_ = val; }
auto music() const -> const std::string& { return music_; }
void set_music(const std::string& val) { music_ = val; }
// We actually change the song only when this value changes
// (allows us to restart the same song)
auto music_count() const -> int { return music_count_; }
void SetMusicCount(int val);
protected:
CameraMode camera_mode_{CameraMode::kFollow};
float vr_near_clip_{4.0f};
float debris_friction_{1.0f};
bool floor_reflection_{};
float debris_kill_height_{-50.0f};
bool happy_thoughts_mode_{};
bool use_fixed_vr_overlay_{};
int music_count_{};
bool music_continuous_{};
std::string music_;
std::vector<float> vr_camera_offset_{0.0f, 0.0f, 0.0f};
std::vector<float> shadow_scale_{1.0f, 1.0f};
std::vector<float> area_of_interest_bounds_{-9999.0f, -9999.0f, -9999.0f,
9999.0f, 9999.0f, 9999.0f};
std::vector<float> shadow_range_{-4.0f, 0.0f, 10.0f, 15.0f};
std::vector<float> shadow_offset_{0.0f, 0.0f, 0.0f};
bool shadow_ortho_{};
bool vr_overlay_center_enabled_{};
std::vector<float> vr_overlay_center_{0.0f, 4.0f, -3.0f};
std::vector<float> tint_{1.1f, 1.0f, 0.9f};
std::vector<float> ambient_color_{1.0f, 1.0f, 1.0f};
std::vector<float> vignette_outer_{0.6f, 0.6f, 0.6f};
std::vector<float> vignette_inner_{0.95f, 0.95f, 0.95f};
bool allow_kick_idle_players_{};
bool slow_motion_{};
bool paused_{};
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_GLOBALS_NODE_H_

View File

@ -0,0 +1,400 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/image_node.h"
#include <string>
#include <vector>
#include "ballistica/graphics/component/simple_component.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/graphics/renderer.h"
#include "ballistica/media/component/model.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
class ImageNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS ImageNode
BA_NODE_CREATE_CALL(CreateImage);
BA_FLOAT_ARRAY_ATTR(scale, scale, SetScale);
BA_FLOAT_ARRAY_ATTR(position, position, SetPosition);
BA_FLOAT_ATTR(opacity, opacity, set_opacity);
BA_FLOAT_ARRAY_ATTR(color, color, SetColor);
BA_FLOAT_ARRAY_ATTR(tint_color, tint_color, SetTintColor);
BA_FLOAT_ARRAY_ATTR(tint2_color, tint2_color, SetTint2Color);
BA_BOOL_ATTR(fill_screen, fill_screen, SetFillScreen);
BA_BOOL_ATTR(has_alpha_channel, has_alpha_channel, set_has_alpha_channel);
BA_BOOL_ATTR(absolute_scale, absolute_scale, set_absolute_scale);
BA_FLOAT_ATTR(tilt_translate, tilt_translate, set_tilt_translate);
BA_FLOAT_ATTR(rotate, rotate, set_rotate);
BA_BOOL_ATTR(premultiplied, premultiplied, set_premultiplied);
BA_STRING_ATTR(attach, GetAttach, SetAttach);
BA_TEXTURE_ATTR(texture, texture, set_texture);
BA_TEXTURE_ATTR(tint_texture, tint_texture, set_tint_texture);
BA_TEXTURE_ATTR(mask_texture, mask_texture, set_mask_texture);
BA_MODEL_ATTR(model_opaque, model_opaque, set_model_opaque);
BA_MODEL_ATTR(model_transparent, model_transparent, set_model_transparent);
BA_FLOAT_ATTR(vr_depth, vr_depth, set_vr_depth);
BA_BOOL_ATTR(host_only, host_only, set_host_only);
BA_BOOL_ATTR(front, front, set_front);
#undef BA_NODE_TYPE_CLASS
ImageNodeType()
: NodeType("image", CreateImage),
scale(this),
position(this),
opacity(this),
color(this),
tint_color(this),
tint2_color(this),
fill_screen(this),
has_alpha_channel(this),
absolute_scale(this),
tilt_translate(this),
rotate(this),
premultiplied(this),
attach(this),
texture(this),
tint_texture(this),
mask_texture(this),
model_opaque(this),
model_transparent(this),
vr_depth(this),
host_only(this),
front(this) {}
};
static NodeType* node_type{};
auto ImageNode::InitType() -> NodeType* {
node_type = new ImageNodeType();
return node_type;
}
ImageNode::ImageNode(Scene* scene) : Node(scene, node_type) {}
ImageNode::~ImageNode() {
if (fill_screen_) scene()->decrement_bg_cover_count();
}
auto ImageNode::GetAttach() const -> std::string {
switch (attach_) {
case Attach::CENTER:
return "center";
case Attach::TOP_LEFT:
return "topLeft";
case Attach::TOP_CENTER:
return "topCenter";
case Attach::TOP_RIGHT:
return "topRight";
case Attach::CENTER_RIGHT:
return "centerRight";
case Attach::BOTTOM_RIGHT:
return "bottomRight";
case Attach::BOTTOM_CENTER:
return "bottomCenter";
case Attach::BOTTOM_LEFT:
return "bottomLeft";
case Attach::CENTER_LEFT:
return "centerLeft";
default:
throw Exception("Invalid attach val in ImageNode "
+ std::to_string(static_cast<int>(attach_)));
}
}
void ImageNode::SetAttach(const std::string& val) {
dirty_ = true;
if (val == "center") {
attach_ = Attach::CENTER;
} else if (val == "topLeft") {
attach_ = Attach::TOP_LEFT;
} else if (val == "topCenter") {
attach_ = Attach::TOP_CENTER;
} else if (val == "topRight") {
attach_ = Attach::TOP_RIGHT;
} else if (val == "centerRight") {
attach_ = Attach::CENTER_RIGHT;
} else if (val == "bottomRight") {
attach_ = Attach::BOTTOM_RIGHT;
} else if (val == "bottomCenter") {
attach_ = Attach::BOTTOM_CENTER;
} else if (val == "bottomLeft") {
attach_ = Attach::BOTTOM_LEFT;
} else if (val == "centerLeft") {
attach_ = Attach::CENTER_LEFT;
} else {
throw Exception("Invalid attach value for ImageNode: " + val);
}
}
void ImageNode::SetTint2Color(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of size 3 for tint2_color");
}
tint2_color_ = vals;
tint2_red_ = tint2_color_[0];
tint2_green_ = tint2_color_[1];
tint2_blue_ = tint2_color_[2];
}
void ImageNode::SetTintColor(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of size 3 for tint_color");
}
tint_color_ = vals;
tint_red_ = tint_color_[0];
tint_green_ = tint_color_[1];
tint_blue_ = tint_color_[2];
}
void ImageNode::SetColor(const std::vector<float>& vals) {
if (vals.size() != 3 && vals.size() != 4) {
throw Exception("Got " + std::to_string(vals.size())
+ " values for 'color'; expected 3 or 4.");
}
red_ = vals[0];
green_ = vals[1];
blue_ = vals[2];
if (vals.size() == 4) {
alpha_ = vals[3];
} else {
alpha_ = 1.0f;
}
color_ = vals;
}
void ImageNode::SetScale(const std::vector<float>& vals) {
if (vals.size() != 1 && vals.size() != 2) {
throw Exception("Expected float array of length 1 or 2 for scale");
}
dirty_ = true;
scale_ = vals;
}
void ImageNode::SetPosition(const std::vector<float>& vals) {
if (vals.size() != 2) {
throw Exception("Expected float array of length 2 for position");
}
dirty_ = true;
position_ = vals;
}
void ImageNode::OnScreenSizeChange() { dirty_ = true; }
void ImageNode::SetFillScreen(bool val) {
bool old = fill_screen_;
fill_screen_ = val;
dirty_ = true;
// Help the scene keep track of stuff that covers the whole background
// (so it knows it doesnt have to clear).
if (!old && fill_screen_) scene()->increment_bg_cover_count();
if (old && !fill_screen_) scene()->decrement_bg_cover_count();
// We keep track of how many full-screen images are present at any given time.
// vr-mode uses this to lock down the overlay layer's position in that case.
}
void ImageNode::Draw(FrameDef* frame_def) {
if (host_only_ && !context().GetHostSession()) return;
bool vr = (IsVRMode());
// In vr mode we use the fixed overlay position if our scene
// is set for that.
bool vr_use_fixed = (scene()->use_fixed_vr_overlay());
// Currently front and vr-fixed are mutually-exclusive.. need to fix.
if (front_) {
vr_use_fixed = false;
}
RenderPass& pass(*(vr_use_fixed ? frame_def->GetOverlayFixedPass()
: front_ ? frame_def->overlay_front_pass()
: frame_def->overlay_pass()));
// If the pass we're drawing into changes dimensions, recalc.
// Otherwise we break if a window is resized.
float screen_width = pass.virtual_width();
float screen_height = pass.virtual_height();
if (dirty_) {
float width = absolute_scale_ ? scale_[0] : screen_height * scale_[0];
float height =
(scale_.size() > 1)
? (absolute_scale_ ? scale_[1] : screen_height * scale_[1])
: width;
float center_x, center_y;
float scale_mult_x = absolute_scale_ ? 1.0f : screen_width;
float scale_mult_y = absolute_scale_ ? 1.0f : screen_height;
float screen_center_x = screen_width / 2;
float screen_center_y = screen_height / 2;
float tx = position_[0];
float ty = position_[1];
if (!absolute_scale_) {
tx *= scale_mult_x;
ty *= scale_mult_y;
}
switch (attach_) {
case Attach::BOTTOM_LEFT:
case Attach::BOTTOM_CENTER:
case Attach::BOTTOM_RIGHT: {
center_y = ty;
break;
}
case Attach::TOP_LEFT:
case Attach::TOP_CENTER:
case Attach::TOP_RIGHT: {
center_y = screen_height + ty;
break;
}
case Attach::CENTER_LEFT:
case Attach::CENTER_RIGHT:
case Attach::CENTER: {
center_y = screen_center_y + ty;
break;
}
}
switch (attach_) {
case Attach::TOP_LEFT:
case Attach::CENTER_LEFT:
case Attach::BOTTOM_LEFT: {
center_x = tx;
break;
}
case Attach::TOP_CENTER:
case Attach::CENTER:
case Attach::BOTTOM_CENTER: {
center_x = screen_center_x + tx;
break;
}
case Attach::TOP_RIGHT:
case Attach::CENTER_RIGHT:
case Attach::BOTTOM_RIGHT: {
center_x = screen_width + tx;
break;
}
}
if (fill_screen_) {
width_ = screen_width;
height_ = screen_height;
center_x_ = width_ * 0.5f;
center_y_ = height_ * 0.5f;
} else {
center_x_ = center_x;
center_y_ = center_y;
width_ = width;
height_ = height;
}
dirty_ = false;
}
float fin_center_x = center_x_;
float fin_center_y = center_y_;
float fin_width = width_;
float fin_height = height_;
// Tilt-translate doesn't happen in vr mode.
if (tilt_translate_ != 0.0f && !vr) {
Vector3f tilt = g_graphics->tilt();
fin_center_x -= tilt.y * tilt_translate_;
fin_center_y += tilt.x * tilt_translate_;
// If we're fullscreen and are tilting, crank our dimensions up
// slightly to account for tiltage.
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
if (fill_screen_) {
float s = 1.0f - tilt_translate_ * 0.2f;
fin_width *= s;
fin_height *= s;
}
#endif // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
}
bool has_alpha_channel = has_alpha_channel_;
float alpha = opacity_ * alpha_;
if (alpha < 0) {
alpha = 0;
}
ModelData* model_opaque_used = nullptr;
if (model_opaque_.exists()) model_opaque_used = model_opaque_->model_data();
ModelData* model_transparent_used = nullptr;
if (model_transparent_.exists()) {
model_transparent_used = model_transparent_->model_data();
}
// If no meshes were provided, use default image models.
if (!model_opaque_.exists() && !model_transparent_.exists()) {
if (vr && fill_screen_) {
#if BA_VR_BUILD
model_opaque_used =
g_media->GetModel(SystemModelID::kImage1x1VRFullScreen);
#else
throw Exception();
#endif // BA_VR_BUILD
} else {
SystemModelID m = fill_screen_ ? SystemModelID::kImage1x1FullScreen
: SystemModelID::kImage1x1;
if (has_alpha_channel) {
model_transparent_used = g_media->GetModel(m);
} else {
model_opaque_used = g_media->GetModel(m);
}
}
}
// Draw opaque portion either opaque or transparent depending on our
// global opacity.
if (model_opaque_used) {
// Draw in opaque pass if possible.
SimpleComponent c(&pass);
bool draw_transparent = (alpha < 0.999f);
// Stuff in the fixed vr overlay pass may inadvertently
// obscure the non-fixed overlay pass, so lets just always draw
// transparent to avoid that.
c.SetTransparent(draw_transparent);
c.SetPremultiplied(premultiplied_);
c.SetTexture(texture_);
c.SetColor(red_, green_, blue_, alpha);
if (tint_texture_.exists()) {
c.SetColorizeTexture(tint_texture_);
c.SetColorizeColor(tint_red_, tint_green_, tint_blue_);
c.SetColorizeColor2(tint2_red_, tint2_green_, tint2_blue_);
}
c.SetMaskTexture(mask_texture_);
c.PushTransform();
c.Translate(fin_center_x, fin_center_y,
vr ? vr_depth_ : g_graphics->overlay_node_z_depth());
if (rotate_ != 0.0f) c.Rotate(rotate_, 0, 0, 1);
c.Scale(fin_width, fin_height, fin_width);
c.DrawModel(model_opaque_used);
c.PopTransform();
c.Submit();
}
// Transparent portion.
if (model_transparent_used) {
SimpleComponent c(&pass);
c.SetTransparent(true);
c.SetPremultiplied(premultiplied_);
c.SetTexture(texture_);
c.SetColor(red_, green_, blue_, alpha);
if (tint_texture_.exists()) {
c.SetColorizeTexture(tint_texture_);
c.SetColorizeColor(tint_red_, tint_green_, tint_blue_);
c.SetColorizeColor2(tint2_red_, tint2_green_, tint2_blue_);
}
c.SetMaskTexture(mask_texture_);
c.PushTransform();
c.Translate(fin_center_x, fin_center_y,
vr ? vr_depth_ : g_graphics->overlay_node_z_depth());
if (rotate_ != 0.0f) c.Rotate(rotate_, 0, 0, 1);
c.Scale(fin_width, fin_height, fin_width);
c.DrawModel(model_transparent_used);
c.PopTransform();
c.Submit();
}
}
} // namespace ballistica

View File

@ -0,0 +1,124 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_IMAGE_NODE_H_
#define BALLISTICA_SCENE_NODE_IMAGE_NODE_H_
#include <string>
#include <vector>
#include "ballistica/media/component/model.h"
#include "ballistica/media/component/texture.h"
#include "ballistica/scene/node/node.h"
namespace ballistica {
// Node used to draw 2d image overlays on-screen.
class ImageNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit ImageNode(Scene* scene);
~ImageNode() override;
void Draw(FrameDef* frame_def) override;
auto scale() const -> std::vector<float> { return scale_; }
void SetScale(const std::vector<float>& scale);
auto position() const -> std::vector<float> { return position_; }
void SetPosition(const std::vector<float>& val);
auto opacity() const -> float { return opacity_; }
void set_opacity(float val) { opacity_ = val; }
auto color() const -> std::vector<float> { return color_; }
void SetColor(const std::vector<float>& val);
auto tint_color() const -> std::vector<float> { return tint_color_; }
void SetTintColor(const std::vector<float>& val);
auto tint2_color() const -> std::vector<float> { return tint2_color_; }
void SetTint2Color(const std::vector<float>& val);
auto fill_screen() const -> bool { return fill_screen_; }
void SetFillScreen(bool val);
auto has_alpha_channel() const -> bool { return has_alpha_channel_; }
void set_has_alpha_channel(bool val) { has_alpha_channel_ = val; }
auto absolute_scale() const -> bool { return absolute_scale_; }
void set_absolute_scale(bool val) {
absolute_scale_ = val;
dirty_ = true;
}
auto tilt_translate() const -> float { return tilt_translate_; }
void set_tilt_translate(float val) { tilt_translate_ = val; }
auto rotate() const -> float { return rotate_; }
void set_rotate(float val) { rotate_ = val; }
auto premultiplied() const -> bool { return premultiplied_; }
void set_premultiplied(bool val) { premultiplied_ = val; }
auto GetAttach() const -> std::string;
void SetAttach(const std::string& val);
auto texture() const -> Texture* { return texture_.get(); }
void set_texture(Texture* t) { texture_ = t; }
auto tint_texture() const -> Texture* { return tint_texture_.get(); }
void set_tint_texture(Texture* t) { tint_texture_ = t; }
auto mask_texture() const -> Texture* { return mask_texture_.get(); }
void set_mask_texture(Texture* t) { mask_texture_ = t; }
auto model_opaque() const -> Model* { return model_opaque_.get(); }
void set_model_opaque(Model* m) { model_opaque_ = m; }
auto model_transparent() const -> Model* { return model_transparent_.get(); }
void set_model_transparent(Model* m) {
model_transparent_ = m;
dirty_ = true;
}
auto vr_depth() const -> float { return vr_depth_; }
void set_vr_depth(float val) { vr_depth_ = val; }
void OnScreenSizeChange() override;
auto host_only() const -> bool { return host_only_; }
void set_host_only(bool val) { host_only_ = val; }
auto front() const -> bool { return front_; }
void set_front(bool val) { front_ = val; }
private:
enum class Attach {
CENTER,
TOP_LEFT,
TOP_CENTER,
TOP_RIGHT,
CENTER_RIGHT,
BOTTOM_RIGHT,
BOTTOM_CENTER,
BOTTOM_LEFT,
CENTER_LEFT
};
bool host_only_{};
bool front_{};
float vr_depth_{};
std::vector<float> scale_{1.0f, 1.0f};
std::vector<float> position_{0.0f, 0.0f};
std::vector<float> color_{1.0f, 1.0f, 1.0f};
std::vector<float> tint_color_{1.0f, 1.0f, 1.0f};
std::vector<float> tint2_color_{1.0f, 1.0f, 1.0f};
Object::Ref<Texture> texture_;
Object::Ref<Texture> tint_texture_;
Object::Ref<Texture> mask_texture_;
Object::Ref<Model> model_opaque_;
Object::Ref<Model> model_transparent_;
bool fill_screen_{};
bool has_alpha_channel_{true};
bool dirty_{true};
float opacity_{1.0f};
Attach attach_{Attach::CENTER};
bool absolute_scale_{true};
float center_x_{};
float center_y_{};
float width_{};
float height_{};
float tilt_translate_{};
float rotate_{};
bool premultiplied_{};
float red_{1.0f};
float green_{1.0f};
float blue_{1.0f};
float alpha_{1.0f};
float tint_red_{1.0f};
float tint_green_{1.0f};
float tint_blue_{1.0f};
float tint2_red_{1.0f};
float tint2_green_{1.0f};
float tint2_blue_{1.0f};
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_IMAGE_NODE_H_

View File

@ -0,0 +1,153 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/light_node.h"
#include <algorithm>
#include "ballistica/dynamics/bg/bg_dynamics_volume_light.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
class LightNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS LightNode
BA_NODE_CREATE_CALL(CreateLight);
BA_FLOAT_ARRAY_ATTR(position, position, SetPosition);
BA_FLOAT_ATTR(intensity, intensity, SetIntensity);
BA_FLOAT_ATTR(volume_intensity_scale, volume_intensity_scale,
SetVolumeIntensityScale);
BA_FLOAT_ARRAY_ATTR(color, color, SetColor);
BA_FLOAT_ATTR(radius, radius, SetRadius);
BA_BOOL_ATTR(lights_volumes, lights_volumes, set_lights_volumes);
BA_BOOL_ATTR(height_attenuated, height_attenuated, set_height_attenuated);
#undef BA_NODE_TYPE_CLASS
LightNodeType()
: NodeType("light", CreateLight),
position(this),
intensity(this),
volume_intensity_scale(this),
color(this),
radius(this),
lights_volumes(this),
height_attenuated(this) {}
};
static NodeType* node_type{};
auto LightNode::InitType() -> NodeType* {
node_type = new LightNodeType();
return node_type;
}
LightNode::LightNode(Scene* scene) : Node(scene, node_type) {}
auto LightNode::GetVolumeLightIntensity() -> float {
return intensity_ * volume_intensity_scale_ * 0.02f;
}
void LightNode::Step() {
#if !BA_HEADLESS_BUILD
// create or destroy our light-volume as needed
// (minimize redundant create/destroy/sets this way)
if (lights_volumes_ && !volume_light_.exists()) {
volume_light_ = Object::New<BGDynamicsVolumeLight>();
float i = GetVolumeLightIntensity();
volume_light_->SetColor(color_[0] * i, color_[1] * i, color_[2] * i);
volume_light_->SetPosition(
Vector3f(position_[0], position_[1], position_[2]));
} else if (!lights_volumes_ && volume_light_.exists()) {
volume_light_.Clear();
}
#endif // BA_HEADLESS_BUILD
}
void LightNode::SetRadius(float val) {
radius_ = std::max(0.0f, val);
#if !BA_HEADLESS_BUILD
if (volume_light_.exists()) {
volume_light_->SetRadius(radius_);
}
#endif // BA_HEADLESS_BUILD
}
void LightNode::SetColor(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("expected float array of size 3 for color");
}
color_ = vals;
#if !BA_HEADLESS_BUILD
if (volume_light_.exists()) {
float i = GetVolumeLightIntensity();
volume_light_->SetColor(color_[0] * i, color_[1] * i, color_[2] * i);
}
#endif // BA_HEADLESS_BUILD
}
void LightNode::SetPosition(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("expected float array of size 3 for position");
}
position_ = vals;
#if !BA_HEADLESS_BUILD
shadow_.SetPosition(Vector3f(position_[0], position_[1], position_[2]));
if (volume_light_.exists()) {
volume_light_->SetPosition(
Vector3f(position_[0], position_[1], position_[2]));
}
#endif // BA_HEADLESS_BUILD
}
void LightNode::SetIntensity(float val) {
intensity_ = std::max(0.0f, val);
#if !BA_HEADLESS_BUILD
if (volume_light_.exists()) {
float i = GetVolumeLightIntensity();
volume_light_->SetColor(color_[0] * i, color_[1] * i, color_[2] * i);
}
#endif // BA_HEADLESS_BUILD
}
void LightNode::SetVolumeIntensityScale(float val) {
volume_intensity_scale_ = std::max(0.0f, val);
#if !BA_HEADLESS_BUILD
if (volume_light_.exists()) {
float i = GetVolumeLightIntensity();
volume_light_->SetColor(color_[0] * i, color_[1] * i, color_[2] * i);
}
#endif // BA_HEADLESS_BUILD
}
void LightNode::Draw(FrameDef* frame_def) {
#if !BA_HEADLESS_BUILD
// if we haven't gotten our initial attributes, dont draw
assert(position_.size() == 3);
// if (position_.size() == 0) return;
float s_density, s_scale;
if (height_attenuated_) {
shadow_.GetValues(&s_scale, &s_density);
} else {
s_density = 1.0f;
s_scale = 1.0f;
}
float brightness = s_density * 0.65f * intensity_;
// draw our light on both terrain and objects
g_graphics->DrawBlotchSoft(Vector3f(&position_[0]), 20.0f * radius_ * s_scale,
color_[0] * brightness, color_[1] * brightness,
color_[2] * brightness, 0.0f);
g_graphics->DrawBlotchSoftObj(Vector3f(&position_[0]),
20.0f * radius_ * s_scale,
color_[0] * brightness, color_[1] * brightness,
color_[2] * brightness, 0.0f);
#endif // BA_HEADLESS_BUILD
}
} // namespace ballistica

View File

@ -0,0 +1,54 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_LIGHT_NODE_H_
#define BALLISTICA_SCENE_NODE_LIGHT_NODE_H_
#include <vector>
#include "ballistica/dynamics/bg/bg_dynamics_shadow.h"
#include "ballistica/scene/node/node.h"
namespace ballistica {
// A light source
class LightNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit LightNode(Scene* scene);
void Draw(FrameDef* frame_def) override;
void Step() override;
auto position() const -> std::vector<float> { return position_; }
void SetPosition(const std::vector<float>& val);
auto intensity() const -> float { return intensity_; }
void SetIntensity(float val);
auto volume_intensity_scale() const -> float {
return volume_intensity_scale_;
}
void SetVolumeIntensityScale(float val);
auto color() const -> std::vector<float> { return color_; }
void SetColor(const std::vector<float>& val);
auto radius() const -> float { return radius_; }
void SetRadius(float val);
auto lights_volumes() const -> bool { return lights_volumes_; }
void set_lights_volumes(bool val) { lights_volumes_ = val; }
auto height_attenuated() const -> bool { return height_attenuated_; }
void set_height_attenuated(bool val) { height_attenuated_ = val; }
private:
auto GetVolumeLightIntensity() -> float;
#if !BA_HEADLESS_BUILD
BGDynamicsShadow shadow_{0.2f};
Object::Ref<BGDynamicsVolumeLight> volume_light_;
#endif
std::vector<float> position_ = {0.0f, 0.0f, 0.0f};
std::vector<float> color_ = {1.0f, 1.0f, 1.0f};
float intensity_ = 1.0f;
float volume_intensity_scale_ = 1.0f;
float radius_ = 0.5f;
bool height_attenuated_ = true;
bool lights_volumes_ = true;
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_LIGHT_NODE_H_

View File

@ -0,0 +1,179 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/locator_node.h"
#include "ballistica/graphics/component/simple_component.h"
#include "ballistica/graphics/renderer.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
class LocatorNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS LocatorNode
BA_NODE_CREATE_CALL(CreateLocator);
BA_FLOAT_ARRAY_ATTR(position, position, SetPosition);
BA_BOOL_ATTR(visibility, visibility, set_visibility);
BA_FLOAT_ARRAY_ATTR(size, size, SetSize);
BA_FLOAT_ARRAY_ATTR(color, color, SetColor);
BA_FLOAT_ATTR(opacity, opacity, set_opacity);
BA_BOOL_ATTR(draw_beauty, draw_beauty, set_draw_beauty);
BA_BOOL_ATTR(drawShadow, getDrawShadow, setDrawShadow);
BA_STRING_ATTR(shape, getShape, SetShape);
BA_BOOL_ATTR(additive, getAdditive, setAdditive);
#undef BA_NODE_TYPE_CLASS
LocatorNodeType()
: NodeType("locator", CreateLocator),
position(this),
visibility(this),
size(this),
color(this),
opacity(this),
draw_beauty(this),
drawShadow(this),
shape(this),
additive(this) {}
};
static NodeType* node_type{};
auto LocatorNode::InitType() -> NodeType* {
node_type = new LocatorNodeType();
return node_type;
}
LocatorNode::LocatorNode(Scene* scene) : Node(scene, node_type) {}
auto LocatorNode::getShape() const -> std::string {
switch (shape_) {
case Shape::kBox:
return "box";
case Shape::kCircle:
return "circle";
case Shape::kCircleOutline:
return "circleOutline";
case Shape::kLocator:
return "locator";
default:
throw Exception("Invalid shape val: "
+ std::to_string(static_cast<int>(shape_)));
}
}
void LocatorNode::SetShape(const std::string& val) {
if (val == "box") {
shape_ = Shape::kBox;
} else if (val == "circle") {
shape_ = Shape::kCircle;
} else if (val == "circleOutline") {
shape_ = Shape::kCircleOutline;
} else if (val == "locator") {
shape_ = Shape::kLocator;
} else {
throw Exception("invalid locator shape: " + val);
}
}
void LocatorNode::SetColor(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of size 3 for color");
}
color_ = vals;
}
void LocatorNode::SetPosition(const std::vector<float>& vals) {
if (vals.size() != 3)
throw Exception("Expected float array of size 3 for position");
position_ = vals;
}
void LocatorNode::SetSize(const std::vector<float>& vals) {
if (vals.size() != 1 && vals.size() != 3)
throw Exception("Expected float array of size 1 or 3 for size");
size_ = vals;
if (size_.size() == 1) {
size_.push_back(size_[0]);
size_.push_back(size_[0]);
}
}
void LocatorNode::Draw(FrameDef* frame_def) {
SystemModelID model;
if (shape_ == Shape::kBox) {
model = SystemModelID::kLocatorBox;
} else if (shape_ == Shape::kCircle) {
model = SystemModelID::kLocatorCircle;
} else if (shape_ == Shape::kCircleOutline) {
model = SystemModelID::kLocatorCircleOutline;
} else {
model = SystemModelID::kLocator;
}
SystemTextureID texture;
if (shape_ == Shape::kCircle) {
texture =
additive_ ? SystemTextureID::kCircleNoAlpha : SystemTextureID::kCircle;
} else if (shape_ == Shape::kCircleOutline) {
texture = additive_ ? SystemTextureID::kCircleOutlineNoAlpha
: SystemTextureID::kCircleOutline;
} else {
texture = SystemTextureID::kRGBStripes;
}
bool transparent = false;
if (shape_ == Shape::kCircle || shape_ == Shape::kCircleOutline)
transparent = true;
// beauty
if (draw_beauty_) {
SimpleComponent c(frame_def->beauty_pass());
if (transparent) c.SetTransparent(true);
c.SetColor(color_[0], color_[1], color_[2], opacity_);
c.SetTexture(g_media->GetTexture(texture));
c.PushTransform();
c.Translate(position_[0], position_[1], position_[2]);
c.Scale(size_[0], size_[1], size_[2]);
c.DrawModel(g_media->GetModel(model));
c.PopTransform();
c.Submit();
}
if (draw_shadow_) {
// colored shadow for circle
if (shape_ == Shape::kCircle || shape_ == Shape::kCircleOutline) {
SimpleComponent c(frame_def->light_shadow_pass());
if (transparent) {
c.SetTransparent(true);
if (additive_) {
c.SetPremultiplied(true);
}
}
if (additive_) {
c.SetColor(color_[0] * opacity_, color_[1] * opacity_,
color_[2] * opacity_, 0.0f);
} else {
c.SetColor(color_[0], color_[1], color_[2], opacity_);
}
c.SetTexture(g_media->GetTexture(texture));
c.PushTransform();
c.Translate(position_[0], position_[1], position_[2]);
c.Scale(size_[0], size_[1], size_[2]);
c.DrawModel(g_media->GetModel(model));
c.PopTransform();
c.Submit();
} else {
// simple black shadow for locator/box
SimpleComponent c(frame_def->light_shadow_pass());
c.SetTransparent(true);
c.SetColor(0.4f, 0.4f, 0.4f, 0.7f);
c.PushTransform();
c.Translate(position_[0], position_[1], position_[2]);
c.Scale(size_[0], size_[1], size_[2]);
c.DrawModel(g_media->GetModel(model));
c.PopTransform();
c.Submit();
}
}
}
} // namespace ballistica

View File

@ -0,0 +1,63 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_LOCATOR_NODE_H_
#define BALLISTICA_SCENE_NODE_LOCATOR_NODE_H_
#include <string>
#include <vector>
#include "ballistica/scene/node/node.h"
namespace ballistica {
class LocatorNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit LocatorNode(Scene* scene);
void Draw(FrameDef* frame_def) override;
auto position() const -> std::vector<float> { return position_; }
void SetPosition(const std::vector<float>& vals);
auto visibility() const -> bool { return visibility_; }
void set_visibility(bool val) { visibility_ = val; }
auto size() const -> std::vector<float> { return size_; }
void SetSize(const std::vector<float>& vals);
auto color() const -> std::vector<float> { return color_; }
void SetColor(const std::vector<float>& vals);
auto opacity() const -> float { return opacity_; }
void set_opacity(float val) { opacity_ = val; }
auto draw_beauty() const -> bool { return draw_beauty_; }
void set_draw_beauty(bool val) { draw_beauty_ = val; }
auto getDrawShadow() const -> bool { return draw_shadow_; }
void setDrawShadow(bool val) { draw_shadow_ = val; }
auto getShape() const -> std::string;
void SetShape(const std::string& val);
auto getAdditive() const -> bool { return additive_; }
void setAdditive(bool val) { additive_ = val; }
private:
enum class Shape { kLocator, kBox, kCircle, kCircleOutline };
Shape shape_ = Shape::kLocator;
bool additive_ = false;
std::vector<float> position_ = {0.0f, 0.0f, 0.0f};
std::vector<float> size_ = {1.0f, 1.0f, 1.0f};
std::vector<float> color_ = {1.0f, 1.0f, 1.0f};
bool visibility_ = true;
float opacity_ = 1.0f;
bool draw_beauty_ = true;
bool draw_shadow_ = true;
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_LOCATOR_NODE_H_

View File

@ -0,0 +1,116 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/math_node.h"
#include <algorithm>
#include <cmath>
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
class MathNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS MathNode
BA_NODE_CREATE_CALL(CreateMath);
BA_FLOAT_ARRAY_ATTR_READONLY(output, GetOutput);
BA_FLOAT_ARRAY_ATTR(input1, input_1, set_input_1);
BA_FLOAT_ARRAY_ATTR(input2, input_2, set_input_2);
BA_STRING_ATTR(operation, GetOperation, SetOperation);
#undef BA_NODE_TYPE_CLASS
MathNodeType()
: NodeType("math", CreateMath),
output(this),
input1(this),
input2(this),
operation(this) {}
};
static NodeType* node_type{};
auto MathNode::InitType() -> NodeType* {
node_type = new MathNodeType();
return node_type;
}
MathNode::MathNode(Scene* scene) : Node(scene, node_type) {}
auto MathNode::GetOperation() const -> std::string {
switch (operation_) {
case Operation::kAdd:
return "add";
case Operation::kSubtract:
return "subtract";
case Operation::kMultiply:
return "multiply";
case Operation::kDivide:
return "divide";
case Operation::kSin:
return "sin";
default:
throw Exception("invalid operation: "
+ std::to_string(static_cast<int>(operation_)));
}
}
void MathNode::SetOperation(const std::string& val) {
if (val == "add") {
operation_ = Operation::kAdd;
} else if (val == "subtract") {
operation_ = Operation::kSubtract;
} else if (val == "multiply") {
operation_ = Operation::kMultiply;
} else if (val == "divide") {
operation_ = Operation::kDivide;
} else if (val == "sin") {
operation_ = Operation::kSin;
} else {
throw Exception("Invalid math node op '" + val + "'");
}
}
auto MathNode::GetOutput() -> std::vector<float> {
size_t val_count = std::min(input_1_.size(), input_2_.size());
std::vector<float> outputs(val_count);
switch (operation_) {
case Operation::kAdd: {
for (size_t i = 0; i < val_count; i++) {
outputs[i] = (input_1_[i] + input_2_[i]);
}
break;
}
case Operation::kSubtract: {
for (size_t i = 0; i < val_count; i++) {
outputs[i] = (input_1_[i] - input_2_[i]);
}
break;
}
case Operation::kMultiply: {
for (size_t i = 0; i < val_count; i++) {
outputs[i] = (input_1_[i] * input_2_[i]);
}
break;
}
case Operation::kDivide: {
for (size_t i = 0; i < val_count; i++) {
outputs[i] = (input_1_[i] / input_2_[i]);
}
break;
}
case Operation::kSin: {
for (size_t i = 0; i < val_count; i++) {
outputs[i] = (sinf(input_1_[i]));
}
break;
}
default:
BA_LOG_ONCE("Error: invalid math op in getOutput(): "
+ std::to_string(static_cast<int>(operation_)));
break;
}
return outputs;
}
} // namespace ballistica

View File

@ -0,0 +1,36 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_MATH_NODE_H_
#define BALLISTICA_SCENE_NODE_MATH_NODE_H_
#include <string>
#include <vector>
#include "ballistica/scene/node/node.h"
namespace ballistica {
// An node used to create simple mathematical relationships via
// attribute connections
class MathNode : public Node {
public:
static auto InitType() -> NodeType*;
explicit MathNode(Scene* scene);
auto GetOutput() -> std::vector<float>;
auto input_1() const -> const std::vector<float>& { return input_1_; }
void set_input_1(const std::vector<float>& vals) { input_1_ = vals; }
auto input_2() const -> std::vector<float> { return input_2_; }
void set_input_2(const std::vector<float>& vals) { input_2_ = vals; }
auto GetOperation() const -> std::string;
void SetOperation(const std::string& val);
private:
enum class Operation { kAdd, kSubtract, kMultiply, kDivide, kSin };
std::vector<float> input_1_;
std::vector<float> input_2_;
Operation operation_ = Operation::kAdd;
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_MATH_NODE_H_

View File

@ -0,0 +1,425 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/node.h"
#include "ballistica/dynamics/part.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/python/class/python_class_node.h"
#include "ballistica/python/python.h"
#include "ballistica/python/python_context_call.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_attribute_connection.h"
#include "ballistica/scene/node/node_type.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
NodeType::~NodeType() {
Log("ERROR: SHOULD NOT BE DESTRUCTING A TYPE type=(" + name_ + ")");
}
Node::Node(Scene* scene_in, NodeType* node_type)
: node_type_(node_type), scene_(scene_in) {}
void Node::AddToScene(Scene* scene) {
// we should have already set our scene ptr in our constructor;
// now we add ourself to its lists..
// (can't create strong refs in constructors)
assert(scene_ == scene);
assert(id_ == 0);
id_ = scene->next_node_id_++;
our_iterator =
scene->nodes_.insert(scene->nodes_.end(), Object::Ref<Node>(this));
if (GameStream* os = scene->GetGameStream()) {
os->AddNode(this);
}
}
Node::~Node() {
// Kill any incoming/outgoing attr connections.
for (auto& i : attribute_connections_incoming_) {
NodeAttributeConnection* a = i.second.get();
assert(a && a->src_node.exists());
// Remove from src node's outgoing list.
a->src_node->attribute_connections_.erase(a->src_iterator);
}
// Kill all refs on our side; this should kill the connections.
attribute_connections_incoming_.clear();
for (auto& attribute_connection : attribute_connections_) {
NodeAttributeConnection* a = attribute_connection.get();
assert(a && a->dst_node.exists());
// Remove from dst node's incoming list.
auto j =
a->dst_node->attribute_connections_incoming_.find(a->dst_attr_index);
assert(j != a->dst_node->attribute_connections_incoming_.end());
a->dst_node->attribute_connections_incoming_.erase(j);
}
// Kill all refs on our side; should kill the connections.
attribute_connections_.clear();
// NOTE: We no longer run death-actions or kill dependent-nodes here in our
// destructor; we allow the scene to do that to keep things cleaner.
// Release our ref to ourself if we have one.
if (py_ref_) {
Py_DECREF(py_ref_);
}
// If we were going to an output stream, inform them of our demise.
assert(scene());
if (GameStream* output_stream = scene()->GetGameStream()) {
output_stream->RemoveNode(this);
}
}
auto Node::GetResyncDataSize() -> int { return 0; }
auto Node::GetResyncData() -> std::vector<uint8_t> {
return std::vector<uint8_t>();
}
void Node::ApplyResyncData(const std::vector<uint8_t>& data) {}
void Node::Draw(FrameDef* frame_def) {}
void Node::OnCreate() {}
auto Node::GetObjectDescription() const -> std::string {
return "<ballistica::Node #" + std::to_string(id()) + " \""
+ (label().empty() ? type()->name() : label()) + "\">";
}
auto Node::HasAttribute(const std::string& name) const -> bool {
return type()->HasAttribute(name);
}
auto Node::GetAttribute(const std::string& name) -> NodeAttribute {
assert(type());
return {this, type()->GetAttribute(name)};
}
auto Node::GetAttribute(int index) -> NodeAttribute {
assert(type());
return {this, type()->GetAttribute(index)};
}
void Node::ConnectAttribute(NodeAttributeUnbound* src_attr, Node* dst_node,
NodeAttributeUnbound* dst_attr) {
// This is a no-op if the scene is shutting down.
if (scene() == nullptr || scene()->shutting_down()) {
return;
}
assert(dst_node);
assert(src_attr && dst_attr);
assert(src_attr->node_type() == type());
assert(dst_node->type() == dst_attr->node_type());
assert(!scene()->in_step());
bool allow = false;
// Currently limiting to certain types;
// Will wait and see on other types.
// A texture/etc attr might not behave well if updated with the same
// value every step.. hmmm.
{
switch (src_attr->type()) {
// Allow bools, ints, and floats to connect to each other
case NodeAttributeType::kBool:
case NodeAttributeType::kInt:
case NodeAttributeType::kFloat:
switch (dst_attr->type()) {
case NodeAttributeType::kBool:
case NodeAttributeType::kInt:
case NodeAttributeType::kFloat:
allow = true;
break;
default:
break;
}
break;
case NodeAttributeType::kString:
// Allow strings to connect to other strings (new in protocol 31).
if (dst_attr->type() == NodeAttributeType::kString) {
allow = true;
}
break;
case NodeAttributeType::kIntArray:
case NodeAttributeType::kFloatArray:
case NodeAttributeType::kTexture:
// Allow these types to connect to other attrs of the same type.
if (src_attr->type() == dst_attr->type()) allow = true;
break;
default:
break;
}
}
if (!allow) {
throw Exception("Attribute connections from " + src_attr->GetTypeName()
+ " to " + dst_attr->GetTypeName()
+ " attrs are not allowed.");
}
// Ok lets do this.
// Disconnect any existing connection to the dst attr.
dst_attr->DisconnectIncoming(dst_node);
auto a(Object::New<NodeAttributeConnection>());
// Store refs to the connection with both the source and dst nodes.
a->src_iterator =
attribute_connections_.insert(attribute_connections_.end(), a);
dst_node->attribute_connections_incoming_[dst_attr->index()] = a;
a->src_node = this;
a->src_attr_index = src_attr->index();
a->dst_node = dst_node;
a->dst_attr_index = dst_attr->index();
a->Update();
}
void Node::UpdateConnections() {
for (auto& attribute_connection : attribute_connections_) {
// Connections should go away when either node dies; make sure that's
// working.
assert(attribute_connection->src_node.exists()
&& attribute_connection->dst_node.exists());
attribute_connection->Update();
}
}
void Node::AddNodeDeathAction(PyObject* call_obj) {
death_actions_.push_back(Object::New<PythonContextCall>(call_obj));
}
void Node::AddDependentNode(Node* node) {
assert(node);
if (node->scene() != scene()) {
throw Exception("Nodes belong to different Scenes");
}
// While we're here lets prune any dead nodes from our list.
// (so if we add/destroy dependents repeatedly we don't build up a giant
// vector of dead ones)
if (!dependent_nodes_.empty()) {
std::vector<Object::WeakRef<Node> > live_nodes;
for (auto& dependent_node : dependent_nodes_) {
if (dependent_node.exists()) live_nodes.push_back(dependent_node);
}
dependent_nodes_.swap(live_nodes);
}
dependent_nodes_.emplace_back(node);
}
void Node::SetDelegate(PyObject* delegate_obj) {
if (delegate_obj != nullptr && delegate_obj != Py_None) {
delegate_.Steal(PyWeakref_NewRef(delegate_obj, nullptr));
} else {
delegate_.Release();
}
}
auto Node::GetPyRef(bool new_ref) -> PyObject* {
assert(InGameThread());
if (py_ref_ == nullptr) {
py_ref_ = PythonClassNode::Create(this);
}
if (new_ref) {
Py_INCREF(py_ref_);
}
return py_ref_;
}
auto Node::GetDelegate() -> PyObject* {
PyObject* ref = delegate_.get();
if (!ref) {
return nullptr;
}
return PyWeakref_GetObject(ref);
}
void Node::DispatchNodeMessage(const char* buffer) {
assert(this);
assert(buffer);
if (scene_->shutting_down()) {
return;
}
// If noone else has handled it, pass it to our low-level handler.
HandleMessage(buffer);
}
void Node::DispatchOutOfBoundsMessage() {
PythonRef instance;
{
Python::ScopedCallLabel label("OutOfBoundsMessage instantiation");
instance = g_python->obj(Python::ObjID::kOutOfBoundsMessageClass).Call();
}
if (instance.exists()) {
DispatchUserMessage(instance.get(), "Node OutOfBoundsMessage dispatch");
} else {
Log("Error creating OutOfBoundsMessage");
}
}
void Node::DispatchPickUpMessage(Node* node) {
assert(node);
PythonRef args(Py_BuildValue("(O)", node->BorrowPyRef()), PythonRef::kSteal);
PythonRef instance;
{
Python::ScopedCallLabel label("PickUpMessage instantiation");
instance = g_python->obj(Python::ObjID::kPickUpMessageClass).Call(args);
}
if (instance.exists()) {
DispatchUserMessage(instance.get(), "Node PickUpMessage dispatch");
} else {
Log("Error creating PickUpMessage");
}
}
void Node::DispatchDropMessage() {
PythonRef instance;
{
Python::ScopedCallLabel label("DropMessage instantiation");
instance = g_python->obj(Python::ObjID::kDropMessageClass).Call();
}
if (instance.exists()) {
DispatchUserMessage(instance.get(), "Node DropMessage dispatch");
} else {
Log("Error creating DropMessage");
}
}
void Node::DispatchPickedUpMessage(Node* by_node) {
assert(by_node);
PythonRef args(Py_BuildValue("(O)", by_node->BorrowPyRef()),
PythonRef::kSteal);
PythonRef instance;
{
Python::ScopedCallLabel label("PickedUpMessage instantiation");
instance = g_python->obj(Python::ObjID::kPickedUpMessageClass).Call(args);
}
if (instance.exists()) {
DispatchUserMessage(instance.get(), "Node PickedUpMessage dispatch");
} else {
Log("Error creating PickedUpMessage");
}
}
void Node::DispatchDroppedMessage(Node* by_node) {
assert(by_node);
PythonRef args(Py_BuildValue("(O)", by_node->BorrowPyRef()),
PythonRef::kSteal);
PythonRef instance;
{
Python::ScopedCallLabel label("DroppedMessage instantiation");
instance = g_python->obj(Python::ObjID::kDroppedMessageClass).Call(args);
}
if (instance.exists()) {
DispatchUserMessage(instance.get(), "Node DroppedMessage dispatch");
} else {
Log("Error creating DroppedMessage");
}
}
void Node::DispatchShouldShatterMessage() {
PythonRef instance;
{
Python::ScopedCallLabel label("ShouldShatterMessage instantiation");
instance = g_python->obj(Python::ObjID::kShouldShatterMessageClass).Call();
}
if (instance.exists()) {
DispatchUserMessage(instance.get(), "Node ShouldShatterMessage dispatch");
} else {
Log("Error creating ShouldShatterMessage");
}
}
void Node::DispatchImpactDamageMessage(float intensity) {
PythonRef args(Py_BuildValue("(f)", intensity), PythonRef::kSteal);
PythonRef instance;
{
Python::ScopedCallLabel label("ImpactDamageMessage instantiation");
instance =
g_python->obj(Python::ObjID::kImpactDamageMessageClass).Call(args);
}
if (instance.exists()) {
DispatchUserMessage(instance.get(), "Node ImpactDamageMessage dispatch");
} else {
Log("Error creating ImpactDamageMessage");
}
}
void Node::DispatchUserMessage(PyObject* obj, const char* label) {
assert(InGameThread());
if (scene_->shutting_down()) {
return;
}
ScopedSetContext cp(context());
PyObject* delegate = GetDelegate();
if (delegate && delegate != Py_None) {
try {
PyObject* handlemessage_obj =
PyObject_GetAttrString(delegate, "handlemessage");
if (!handlemessage_obj) {
PyErr_Clear();
throw Exception("No 'handlemessage' found on delegate object for '"
+ type()->name() + "' node ("
+ Python::ObjToString(delegate) + ")");
}
PythonRef c(handlemessage_obj, PythonRef::kSteal);
{
Python::ScopedCallLabel lscope(label);
c.Call(PythonRef(Py_BuildValue("(O)", obj), PythonRef::kSteal));
}
} catch (const std::exception& e) {
Log(std::string("Error in handlemessage() with message ")
+ PythonRef(obj, PythonRef::kAcquire).Str() + ": '" + e.what() + "'");
}
}
}
void Node::HandleMessage(const char* data_in) {}
void Node::UpdatePartBirthTimes() {
for (auto&& i : parts_) {
i->UpdateBirthTime();
}
}
void Node::CheckBodies() {
for (auto&& i : parts_) {
i->CheckBodies();
}
}
auto NodeType::GetAttributeNames() const -> std::vector<std::string> {
std::vector<std::string> names;
for (auto&& i : attributes_by_name_) {
names.push_back(i.second->name());
}
return names;
}
void Node::ListAttributes(std::list<std::string>* attrs) {
attrs->clear();
// New attrs.
std::vector<std::string> type_attrs = type()->GetAttributeNames();
for (auto&& i : type_attrs) {
attrs->push_back(i);
}
}
void Node::GetRigidBodyPickupLocations(int id, float* pos_obj, float* pos_char,
float* hand_offset_1,
float* hand_offset_2) {
pos_obj[0] = pos_obj[1] = pos_obj[2] = 0;
pos_char[0] = pos_char[1] = pos_char[2] = 0;
hand_offset_1[0] = hand_offset_1[1] = hand_offset_1[2] = 0;
hand_offset_2[0] = hand_offset_2[1] = hand_offset_2[2] = 0;
}
} // namespace ballistica

View File

@ -0,0 +1,226 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SCENE_NODE_NODE_H_
#define BALLISTICA_SCENE_NODE_NODE_H_
#include <list>
#include <map>
#include <string>
#include <vector>
#include "ballistica/ballistica.h"
#include "ballistica/core/context.h"
#include "ballistica/core/object.h"
#include "ballistica/python/python_ref.h"
namespace ballistica {
// Define a static creation call for this node type
#define BA_NODE_CREATE_CALL(FUNC) \
static auto FUNC(Scene* sg)->Node* { \
return Object::NewDeferred<BA_NODE_TYPE_CLASS>(sg); \
}
typedef std::list<Object::Ref<Node> > NodeList;
// Base node class.
class Node : public Object {
public:
Node(Scene* scene, NodeType* node_type);
~Node() override;
auto id() const -> int64_t {
return id_;
} // Return the node's id in its scene.
virtual void Step() {} // Called for each step of the sim.
virtual void OnScreenSizeChange() {} // Called when screen size changes.
virtual void OnLanguageChange() {} // Called when the language changes.
virtual void OnGraphicsQualityChanged(GraphicsQuality q) {}
// The node can rule out collisions between particular bodies using this.
virtual auto PreFilterCollision(RigidBody* b1, RigidBody* r2) -> bool {
return true;
}
// Pull a node type out of a buffer.
static auto extract_node_message_type(const char** b) -> NodeMessageType {
auto t = static_cast<NodeMessageType>(**b);
(*b) += 1;
return t;
}
void ConnectAttribute(NodeAttributeUnbound* src_attr, Node* dst_node,
NodeAttributeUnbound* dst_attr);
// Return an attribute by name.
auto GetAttribute(const std::string& name) -> NodeAttribute;
// Return an attribute by index.
auto GetAttribute(int index) -> NodeAttribute;
void SetDelegate(PyObject* delegate_obj);
auto NewPyRef() -> PyObject* { return GetPyRef(true); }
auto BorrowPyRef() -> PyObject* { return GetPyRef(false); }
// Return the delegate, or nullptr if it doesn't have one
// (or if the delegate has since died).
auto GetDelegate() -> PyObject*;
void AddNodeDeathAction(PyObject* call_obj);
// Add a node to auto-kill when this one dies.
void AddDependentNode(Node* node);
// Update birth times for all the node's parts.
// This should be done when teleporting or otherwise spawning at
// a new location.
void UpdatePartBirthTimes();
// Retrieve an existing part from a node.
auto GetPart(unsigned int id) -> Part* {
assert(id < parts_.size());
return parts_[id];
}
// Used by RigidBodies when adding themselves to the part.
auto AddPart(Part* part_in) -> int {
parts_.push_back(part_in);
return static_cast<int>(parts_.size() - 1);
}
// Used to send messages to a node
void DispatchNodeMessage(const char* buffer);
// Used to send custom user messages to a node
// returns true if handled.
void DispatchUserMessage(PyObject* obj, const char* label);
void DispatchOutOfBoundsMessage();
void DispatchPickedUpMessage(Node* n);
void DispatchDroppedMessage(Node* n);
void DispatchPickUpMessage(Node* n);
void DispatchDropMessage();
void DispatchShouldShatterMessage();
void DispatchImpactDamageMessage(float intensity);
// Utility function to get a rigid body.
virtual auto GetRigidBody(int id) -> RigidBody* { return nullptr; }
// Given a rigid body, return the relative position where it should be picked
// up from.
virtual void GetRigidBodyPickupLocations(int id, float* posObj,
float* pos_char,
float* hand_offset_1,
float* hand_offset_2);
// Called for each Node when it should render itself.
virtual void Draw(FrameDef* frame_def);
// Called for each node once construction is completed
// this can be a good time to create things from the initial attr set, etc
virtual void OnCreate();
auto scene() const -> Scene* {
assert(scene_);
return scene_;
}
// Used to re-sync client versions of a node from the host version.
virtual auto GetResyncDataSize() -> int;
virtual auto GetResyncData() -> std::vector<uint8_t>;
virtual void ApplyResyncData(const std::vector<uint8_t>& data);
auto context() const -> const Context& { return context_; }
// Node labels are purely for local debugging - they aren't unique or sent
// across the network or anything.
void set_label(const std::string& label) { label_ = label; }
auto label() const -> const std::string& { return label_; }
void ListAttributes(std::list<std::string>* attrs);
auto type() const -> NodeType* {
assert(node_type_);
return node_type_;
}
auto HasAttribute(const std::string& name) const -> bool;
auto has_py_ref() -> bool { return (py_ref_ != nullptr); }
void UpdateConnections();
auto iterator() -> NodeList::iterator { return our_iterator; }
void CheckBodies();
#if BA_DEBUG_BUILD
#define BA_DEBUG_CHECK_BODIES() CheckBodies()
#else
#define BA_DEBUG_CHECK_BODIES() ((void)0)
#endif
auto GetObjectDescription() const -> std::string override;
auto parts() const -> const std::vector<Part*>& { return parts_; }
auto death_actions() const
-> const std::vector<Object::Ref<PythonContextCall> >& {
return death_actions_;
}
auto dependent_nodes() const -> const std::vector<Object::WeakRef<Node> >& {
return dependent_nodes_;
}
auto attribute_connections() const
-> const std::list<Object::Ref<NodeAttributeConnection> >& {
return attribute_connections_;
}
auto attribute_connections_incoming() const
-> const std::map<int, Object::Ref<NodeAttributeConnection> >& {
return attribute_connections_incoming_;
}
auto stream_id() const -> int64_t { return stream_id_; }
void set_stream_id(int64_t val) {
assert(stream_id_ == -1);
stream_id_ = val;
}
void clear_stream_id() {
assert(stream_id_ != -1);
stream_id_ = -1;
}
// Return a reference to a python wrapper for this node,
// creating one if need be.
auto GetPyRef(bool new_ref = true) -> PyObject*;
void AddToScene(Scene* scene);
// Called for each message received by an Node.
virtual void HandleMessage(const char* buffer);
private:
int64_t stream_id_{-1};
NodeType* node_type_ = nullptr;
PyObject* py_ref_ = nullptr;
// FIXME - We can get by with *just* a pointer to our scene
// if we add a way to pull context from a scene.
Context context_;
Scene* scene_{};
std::string label_;
std::vector<Object::WeakRef<Node> > dependent_nodes_;
std::vector<Part*> parts_;
int64_t id_{};
NodeList::iterator our_iterator;
// Put this stuff at the bottom so it gets killed first
PythonRef delegate_;
std::vector<Object::Ref<PythonContextCall> > death_actions_;
// Outgoing attr connections in order created.
std::list<Object::Ref<NodeAttributeConnection> > attribute_connections_;
// Incoming attr connections by attr index.
std::map<int, Object::Ref<NodeAttributeConnection> >
attribute_connections_incoming_;
friend class NodeAttributeUnbound;
};
} // namespace ballistica
#endif // BALLISTICA_SCENE_NODE_NODE_H_

View File

@ -0,0 +1,270 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene/node/node_attribute.h"
#include <utility>
#include "ballistica/scene/node/node.h"
#include "ballistica/scene/node/node_attribute_connection.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
auto NodeAttributeUnbound::GetNodeAttributeTypeName(NodeAttributeType t)
-> std::string {
switch (t) {
case NodeAttributeType::kFloat:
return "float";
case NodeAttributeType::kFloatArray:
return "float-array";
case NodeAttributeType::kInt:
return "int";
case NodeAttributeType::kIntArray:
return "int-array";
case NodeAttributeType::kBool:
return "bool";
case NodeAttributeType::kString:
return "string";
case NodeAttributeType::kNode:
return "node";
case NodeAttributeType::kNodeArray:
return "node-array";
case NodeAttributeType::kPlayer:
return "player";
case NodeAttributeType::kMaterialArray:
return "material-array";
case NodeAttributeType::kTexture:
return "texture";
case NodeAttributeType::kTextureArray:
return "texture-array";
case NodeAttributeType::kSound:
return "sound";
case NodeAttributeType::kSoundArray:
return "sound-array";
case NodeAttributeType::kModel:
return "model";
case NodeAttributeType::kModelArray:
return "model-array";
case NodeAttributeType::kCollideModel:
return "collide-model";
case NodeAttributeType::kCollideModelArray:
return "collide-model-array";
default:
Log("Error: Unknown attr type name: "
+ std::to_string(static_cast<int>(t)));
return "unknown";
}
}
NodeAttributeUnbound::NodeAttributeUnbound(NodeType* node_type,
NodeAttributeType type,
std::string name, uint32_t flags)
: node_type_(node_type),
type_(type),
name_(std::move(name)),
flags_(flags) {
assert(node_type);
node_type->attributes_by_name_[name_] = this;
index_ = static_cast<int>(node_type->attributes_by_index_.size());
node_type->attributes_by_index_.push_back(this);
}
void NodeAttributeUnbound::NotReadableError(Node* node) {
throw Exception("Attribute '" + name() + "' on " + node->type()->name()
+ " node is not readable");
}
void NodeAttributeUnbound::NotWritableError(Node* node) {
throw Exception("Attribute '" + name() + "' on " + node->type()->name()
+ " node is not writable");
}
void NodeAttributeUnbound::DisconnectIncoming(Node* node) {
assert(node);
auto i = node->attribute_connections_incoming().find(index());
if (i != node->attribute_connections_incoming().end()) {
NodeAttributeConnection* a = i->second.get();
#if BA_DEBUG_BUILD
Object::WeakRef<NodeAttributeConnection> test_ref(a);
#endif
assert(a && a->src_node.exists());
// Remove from src node's outgoing list.
a->src_node->attribute_connections_.erase(a->src_iterator);
// Remove from our incoming list; this should kill the connection.
node->attribute_connections_incoming_.erase(i);
#if BA_DEBUG_BUILD
if (test_ref.exists()) {
Log("Error: Attr connection still exists after ref releases!");
}
#endif
}
}
auto NodeAttributeUnbound::GetAsFloat(Node* node) -> float {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a float.");
}
void NodeAttributeUnbound::Set(Node* node, float value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a float.");
}
auto NodeAttributeUnbound::GetAsInt(Node* node) -> int64_t {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as an int.");
}
void NodeAttributeUnbound::Set(Node* node, int64_t value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as an int.");
}
auto NodeAttributeUnbound::GetAsBool(Node* node) -> bool {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a bool.");
}
void NodeAttributeUnbound::Set(Node* node, bool value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a bool.");
}
auto NodeAttributeUnbound::GetAsString(Node* node) -> std::string {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a string.");
}
void NodeAttributeUnbound::Set(Node* node, const std::string& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a string.");
}
auto NodeAttributeUnbound::GetAsFloats(Node* node) -> std::vector<float> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a float array.");
}
void NodeAttributeUnbound::Set(Node* node, const std::vector<float>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a float array.");
}
auto NodeAttributeUnbound::GetAsInts(Node* node) -> std::vector<int64_t> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as an int array.");
}
void NodeAttributeUnbound::Set(Node* node, const std::vector<int64_t>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as an int array.");
}
auto NodeAttributeUnbound::GetAsNode(Node* node) -> Node* {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a node.");
}
void NodeAttributeUnbound::Set(Node* node, Node* value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a node.");
}
auto NodeAttributeUnbound::GetAsNodes(Node* node) -> std::vector<Node*> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a node array.");
}
void NodeAttributeUnbound::Set(Node* node, const std::vector<Node*>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a node array.");
}
auto NodeAttributeUnbound::GetAsPlayer(Node* node) -> Player* {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a player.");
}
void NodeAttributeUnbound::Set(Node* node, Player* value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a player.");
}
auto NodeAttributeUnbound::GetAsMaterials(Node* node)
-> std::vector<Material*> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a material array.");
}
void NodeAttributeUnbound::Set(Node* node,
const std::vector<Material*>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a material array.");
}
auto NodeAttributeUnbound::GetAsTexture(Node* node) -> Texture* {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a texture.");
}
void NodeAttributeUnbound::Set(Node* node, Texture* value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a texture.");
}
auto NodeAttributeUnbound::GetAsTextures(Node* node) -> std::vector<Texture*> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a texture array.");
}
void NodeAttributeUnbound::Set(Node* node, const std::vector<Texture*>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a texture array.");
}
auto NodeAttributeUnbound::GetAsSound(Node* node) -> Sound* {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a sound.");
}
void NodeAttributeUnbound::Set(Node* node, Sound* value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a sound.");
}
auto NodeAttributeUnbound::GetAsSounds(Node* node) -> std::vector<Sound*> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a sound array.");
}
void NodeAttributeUnbound::Set(Node* node, const std::vector<Sound*>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a sound array.");
}
auto NodeAttributeUnbound::GetAsModel(Node* node) -> Model* {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a model.");
}
void NodeAttributeUnbound::Set(Node* node, Model* value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a model.");
}
auto NodeAttributeUnbound::GetAsModels(Node* node) -> std::vector<Model*> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a model array.");
}
void NodeAttributeUnbound::Set(Node* node, const std::vector<Model*>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a model array.");
}
auto NodeAttributeUnbound::GetAsCollideModel(Node* node) -> CollideModel* {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a collide-model.");
}
void NodeAttributeUnbound::Set(Node* node, CollideModel* value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a collide-model.");
}
auto NodeAttributeUnbound::GetAsCollideModels(Node* node)
-> std::vector<CollideModel*> {
throw Exception("Can't get attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a collide-model array.");
}
void NodeAttributeUnbound::Set(Node* node,
const std::vector<CollideModel*>& value) {
throw Exception("Can't set attr '" + name() + "' on node type '"
+ node_type()->name() + "' as a collide-model array.");
}
} // namespace ballistica

Some files were not shown because too many files have changed in this diff Show More