mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-24 16:06:51 +08:00
Making more of the C++ layer buildable
This commit is contained in:
parent
8198788228
commit
ae1f93b87f
@ -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"
|
||||
}
|
||||
4
.idea/dictionaries/ericf.xml
generated
4
.idea/dictionaries/ericf.xml
generated
@ -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>
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
51
src/ballistica/audio/al_sys.cc
Normal file
51
src/ballistica/audio/al_sys.cc
Normal 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
|
||||
41
src/ballistica/audio/al_sys.h
Normal file
41
src/ballistica/audio/al_sys.h
Normal 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_
|
||||
180
src/ballistica/audio/audio.cc
Normal file
180
src/ballistica/audio/audio.cc
Normal 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
|
||||
82
src/ballistica/audio/audio.h
Normal file
82
src/ballistica/audio/audio.h
Normal 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_
|
||||
1166
src/ballistica/audio/audio_server.cc
Normal file
1166
src/ballistica/audio/audio_server.cc
Normal file
File diff suppressed because it is too large
Load Diff
142
src/ballistica/audio/audio_server.h
Normal file
142
src/ballistica/audio/audio_server.h
Normal 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_
|
||||
133
src/ballistica/audio/audio_source.cc
Normal file
133
src/ballistica/audio/audio_source.cc
Normal 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
|
||||
71
src/ballistica/audio/audio_source.h
Normal file
71
src/ballistica/audio/audio_source.h
Normal 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_
|
||||
145
src/ballistica/audio/audio_streamer.cc
Normal file
145
src/ballistica/audio/audio_streamer.cc
Normal 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
|
||||
62
src/ballistica/audio/audio_streamer.h
Normal file
62
src/ballistica/audio/audio_streamer.h
Normal 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_
|
||||
136
src/ballistica/audio/ogg_stream.cc
Normal file
136
src/ballistica/audio/ogg_stream.cc
Normal 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), §ion));
|
||||
#else
|
||||
result = static_cast<int>(ov_read(&ogg_file_, pcm + (*size),
|
||||
kAudioStreamBufferSize - (*size), 0, 2, 1,
|
||||
§ion));
|
||||
#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
|
||||
43
src/ballistica/audio/ogg_stream.h
Normal file
43
src/ballistica/audio/ogg_stream.h
Normal 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_
|
||||
376
src/ballistica/dynamics/bg/bg_dynamics.cc
Normal file
376
src/ballistica/dynamics/bg/bg_dynamics.cc
Normal 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
|
||||
84
src/ballistica/dynamics/bg/bg_dynamics.h
Normal file
84
src/ballistica/dynamics/bg/bg_dynamics.h
Normal 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_
|
||||
79
src/ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h
Normal file
79
src/ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h
Normal 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_
|
||||
41
src/ballistica/dynamics/bg/bg_dynamics_fuse.cc
Normal file
41
src/ballistica/dynamics/bg/bg_dynamics_fuse.cc
Normal 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
|
||||
24
src/ballistica/dynamics/bg/bg_dynamics_fuse.h
Normal file
24
src/ballistica/dynamics/bg/bg_dynamics_fuse.h
Normal 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_
|
||||
113
src/ballistica/dynamics/bg/bg_dynamics_fuse_data.h
Normal file
113
src/ballistica/dynamics/bg/bg_dynamics_fuse_data.h
Normal 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_
|
||||
173
src/ballistica/dynamics/bg/bg_dynamics_height_cache.cc
Normal file
173
src/ballistica/dynamics/bg/bg_dynamics_height_cache.cc
Normal 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
|
||||
42
src/ballistica/dynamics/bg/bg_dynamics_height_cache.h
Normal file
42
src/ballistica/dynamics/bg/bg_dynamics_height_cache.h
Normal 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_
|
||||
2657
src/ballistica/dynamics/bg/bg_dynamics_server.cc
Normal file
2657
src/ballistica/dynamics/bg/bg_dynamics_server.cc
Normal file
File diff suppressed because it is too large
Load Diff
170
src/ballistica/dynamics/bg/bg_dynamics_server.h
Normal file
170
src/ballistica/dynamics/bg/bg_dynamics_server.h
Normal 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_
|
||||
53
src/ballistica/dynamics/bg/bg_dynamics_shadow.cc
Normal file
53
src/ballistica/dynamics/bg/bg_dynamics_shadow.cc
Normal 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
|
||||
36
src/ballistica/dynamics/bg/bg_dynamics_shadow.h
Normal file
36
src/ballistica/dynamics/bg/bg_dynamics_shadow.h
Normal 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_
|
||||
46
src/ballistica/dynamics/bg/bg_dynamics_shadow_data.h
Normal file
46
src/ballistica/dynamics/bg/bg_dynamics_shadow_data.h
Normal 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_
|
||||
47
src/ballistica/dynamics/bg/bg_dynamics_volume_light.cc
Normal file
47
src/ballistica/dynamics/bg/bg_dynamics_volume_light.cc
Normal 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
|
||||
25
src/ballistica/dynamics/bg/bg_dynamics_volume_light.h
Normal file
25
src/ballistica/dynamics/bg/bg_dynamics_volume_light.h
Normal 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_
|
||||
30
src/ballistica/dynamics/bg/bg_dynamics_volume_light_data.h
Normal file
30
src/ballistica/dynamics/bg/bg_dynamics_volume_light_data.h
Normal 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_
|
||||
44
src/ballistica/dynamics/collision.h
Normal file
44
src/ballistica/dynamics/collision.h
Normal 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_
|
||||
355
src/ballistica/dynamics/collision_cache.cc
Normal file
355
src/ballistica/dynamics/collision_cache.cc
Normal 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
|
||||
58
src/ballistica/dynamics/collision_cache.h
Normal file
58
src/ballistica/dynamics/collision_cache.h
Normal 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_
|
||||
1114
src/ballistica/dynamics/dynamics.cc
Normal file
1114
src/ballistica/dynamics/dynamics.cc
Normal file
File diff suppressed because it is too large
Load Diff
128
src/ballistica/dynamics/dynamics.h
Normal file
128
src/ballistica/dynamics/dynamics.h
Normal 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_
|
||||
@ -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
|
||||
@ -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_
|
||||
81
src/ballistica/dynamics/material/material.cc
Normal file
81
src/ballistica/dynamics/material/material.cc
Normal 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
|
||||
61
src/ballistica/dynamics/material/material.h
Normal file
61
src/ballistica/dynamics/material/material.h
Normal 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_
|
||||
51
src/ballistica/dynamics/material/material_action.h
Normal file
51
src/ballistica/dynamics/material/material_action.h
Normal 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_
|
||||
227
src/ballistica/dynamics/material/material_component.cc
Normal file
227
src/ballistica/dynamics/material/material_component.cc
Normal 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
|
||||
44
src/ballistica/dynamics/material/material_component.h
Normal file
44
src/ballistica/dynamics/material/material_component.h
Normal 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_
|
||||
90
src/ballistica/dynamics/material/material_condition_node.cc
Normal file
90
src/ballistica/dynamics/material/material_condition_node.cc
Normal 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
|
||||
59
src/ballistica/dynamics/material/material_condition_node.h
Normal file
59
src/ballistica/dynamics/material/material_condition_node.h
Normal 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_
|
||||
93
src/ballistica/dynamics/material/material_context.cc
Normal file
93
src/ballistica/dynamics/material/material_context.cc
Normal 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
|
||||
90
src/ballistica/dynamics/material/material_context.h
Normal file
90
src/ballistica/dynamics/material/material_context.h
Normal 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_
|
||||
@ -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
|
||||
@ -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_
|
||||
41
src/ballistica/dynamics/material/node_mod_material_action.cc
Normal file
41
src/ballistica/dynamics/material/node_mod_material_action.cc
Normal 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
|
||||
28
src/ballistica/dynamics/material/node_mod_material_action.h
Normal file
28
src/ballistica/dynamics/material/node_mod_material_action.h
Normal 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_
|
||||
@ -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
|
||||
@ -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_
|
||||
58
src/ballistica/dynamics/material/part_mod_material_action.cc
Normal file
58
src/ballistica/dynamics/material/part_mod_material_action.cc
Normal 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
|
||||
28
src/ballistica/dynamics/material/part_mod_material_action.h
Normal file
28
src/ballistica/dynamics/material/part_mod_material_action.h
Normal 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_
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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_
|
||||
33
src/ballistica/dynamics/material/sound_material_action.cc
Normal file
33
src/ballistica/dynamics/material/sound_material_action.cc
Normal 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
|
||||
32
src/ballistica/dynamics/material/sound_material_action.h
Normal file
32
src/ballistica/dynamics/material/sound_material_action.h
Normal 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_
|
||||
131
src/ballistica/dynamics/part.cc
Normal file
131
src/ballistica/dynamics/part.cc
Normal 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
|
||||
139
src/ballistica/dynamics/part.h
Normal file
139
src/ballistica/dynamics/part.h
Normal 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_
|
||||
693
src/ballistica/dynamics/rigid_body.cc
Normal file
693
src/ballistica/dynamics/rigid_body.cc
Normal 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
|
||||
215
src/ballistica/dynamics/rigid_body.h
Normal file
215
src/ballistica/dynamics/rigid_body.h
Normal 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_
|
||||
145
src/ballistica/game/connection/connection.h
Normal file
145
src/ballistica/game/connection/connection.h
Normal 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_
|
||||
76
src/ballistica/game/connection/connection_to_client.h
Normal file
76
src/ballistica/game/connection/connection_to_client.h
Normal 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_
|
||||
40
src/ballistica/game/connection/connection_to_client_udp.h
Normal file
40
src/ballistica/game/connection/connection_to_client_udp.h
Normal 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_
|
||||
47
src/ballistica/game/connection/connection_to_host.h
Normal file
47
src/ballistica/game/connection/connection_to_host.h
Normal 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_
|
||||
50
src/ballistica/game/connection/connection_to_host_udp.h
Normal file
50
src/ballistica/game/connection/connection_to_host_udp.h
Normal 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_
|
||||
86
src/ballistica/platform/apple/platform_apple.h
Normal file
86
src/ballistica/platform/apple/platform_apple.h
Normal 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_
|
||||
@ -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
|
||||
@ -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_
|
||||
136
src/ballistica/scene/node/anim_curve_node.cc
Normal file
136
src/ballistica/scene/node/anim_curve_node.cc
Normal 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
|
||||
75
src/ballistica/scene/node/anim_curve_node.h
Normal file
75
src/ballistica/scene/node/anim_curve_node.h
Normal 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_
|
||||
85
src/ballistica/scene/node/bomb_node.cc
Normal file
85
src/ballistica/scene/node/bomb_node.cc
Normal 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
|
||||
31
src/ballistica/scene/node/bomb_node.h
Normal file
31
src/ballistica/scene/node/bomb_node.h
Normal 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_
|
||||
68
src/ballistica/scene/node/combine_node.cc
Normal file
68
src/ballistica/scene/node/combine_node.cc
Normal 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
|
||||
58
src/ballistica/scene/node/combine_node.h
Normal file
58
src/ballistica/scene/node/combine_node.h
Normal 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_
|
||||
224
src/ballistica/scene/node/explosion_node.cc
Normal file
224
src/ballistica/scene/node/explosion_node.cc
Normal 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
|
||||
44
src/ballistica/scene/node/explosion_node.h
Normal file
44
src/ballistica/scene/node/explosion_node.h
Normal 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_
|
||||
691
src/ballistica/scene/node/flag_node.cc
Normal file
691
src/ballistica/scene/node/flag_node.cc
Normal 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
|
||||
77
src/ballistica/scene/node/flag_node.h
Normal file
77
src/ballistica/scene/node/flag_node.h
Normal 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_
|
||||
60
src/ballistica/scene/node/flash_node.cc
Normal file
60
src/ballistica/scene/node/flash_node.cc
Normal 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
|
||||
33
src/ballistica/scene/node/flash_node.h
Normal file
33
src/ballistica/scene/node/flash_node.h
Normal 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_
|
||||
444
src/ballistica/scene/node/globals_node.cc
Normal file
444
src/ballistica/scene/node/globals_node.cc
Normal 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
|
||||
130
src/ballistica/scene/node/globals_node.h
Normal file
130
src/ballistica/scene/node/globals_node.h
Normal 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_
|
||||
400
src/ballistica/scene/node/image_node.cc
Normal file
400
src/ballistica/scene/node/image_node.cc
Normal 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
|
||||
124
src/ballistica/scene/node/image_node.h
Normal file
124
src/ballistica/scene/node/image_node.h
Normal 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_
|
||||
153
src/ballistica/scene/node/light_node.cc
Normal file
153
src/ballistica/scene/node/light_node.cc
Normal 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
|
||||
54
src/ballistica/scene/node/light_node.h
Normal file
54
src/ballistica/scene/node/light_node.h
Normal 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_
|
||||
179
src/ballistica/scene/node/locator_node.cc
Normal file
179
src/ballistica/scene/node/locator_node.cc
Normal 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
|
||||
63
src/ballistica/scene/node/locator_node.h
Normal file
63
src/ballistica/scene/node/locator_node.h
Normal 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_
|
||||
116
src/ballistica/scene/node/math_node.cc
Normal file
116
src/ballistica/scene/node/math_node.cc
Normal 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
|
||||
36
src/ballistica/scene/node/math_node.h
Normal file
36
src/ballistica/scene/node/math_node.h
Normal 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_
|
||||
425
src/ballistica/scene/node/node.cc
Normal file
425
src/ballistica/scene/node/node.cc
Normal 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
|
||||
226
src/ballistica/scene/node/node.h
Normal file
226
src/ballistica/scene/node/node.h
Normal 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_
|
||||
270
src/ballistica/scene/node/node_attribute.cc
Normal file
270
src/ballistica/scene/node/node_attribute.cc
Normal 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
Loading…
x
Reference in New Issue
Block a user