diff --git a/CMakeLists.txt b/CMakeLists.txt index 149faca..7fd96a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,7 +134,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE ) # Installation setup -set(INSTALL_DIR "${CMAKE_SOURCE_DIR}/install/${PROJECT_NAME}/") +set(INSTALL_DIR "${CMAKE_SOURCE_DIR}/demo/addons/${PROJECT_NAME}/") message(STATUS "Install directory: ${INSTALL_DIR}") install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${INSTALL_DIR}/${LIB_DIR} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32ced1e..ad4dc0a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,19 +3,14 @@ target_sources( ${PROJECT_NAME} PRIVATE register_types.cpp - register_types.h steam_audio.cpp - steam_audio.h + steam_audio_server.cpp steam_audio_material.cpp - steam_audio_material.h - steam_audio_listener.h steam_audio_listener.cpp steam_audio_source.cpp - steam_audio_source.h - steam_audio_dynamic_mesh.h steam_audio_dynamic_mesh.cpp steam_audio_static_mesh.cpp - steam_audio_static_mesh.h + steam_audio_source.cpp ) target_include_directories( ${PROJECT_NAME} diff --git a/src/register_types.cpp b/src/register_types.cpp index 8235627..7e78895 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -1,29 +1,36 @@ -#include "register_types.h" +#include "register_types.hpp" using namespace godot; +SteamAudioServer *srv; + void initialize_steam_audio(ModuleInitializationLevel p_level) { - if (p_level!=MODULE_INITIALIZATION_LEVEL_SCENE) { + if (p_level!=MODULE_INITIALIZATION_LEVEL_SCENE && p_level != MODULE_INITIALIZATION_LEVEL_SERVERS) { return; } - register_steam_audio_settings(); - GDREGISTER_CLASS(SteamAudio); - GDREGISTER_CLASS(SteamAudioMaterial); - GDREGISTER_RUNTIME_CLASS(SteamAudioListener); - GDREGISTER_RUNTIME_CLASS(SteamAudioSource); - GDREGISTER_RUNTIME_CLASS(SteamAudioStaticMesh) - GDREGISTER_RUNTIME_CLASS(SteamAudioDynamicMesh) + if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) { + GDREGISTER_CLASS(SteamAudioServer); + srv = memnew(SteamAudioServer); + } + if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { + register_steam_audio_settings(); + GDREGISTER_RUNTIME_CLASS(SteamAudioMaterial); + GDREGISTER_RUNTIME_CLASS(SteamAudioListener); + GDREGISTER_RUNTIME_CLASS(SteamAudioSource); + GDREGISTER_RUNTIME_CLASS(SteamAudioStaticMesh); + GDREGISTER_RUNTIME_CLASS(SteamAudioDynamicMesh); + } } void uninitialize_steam_audio(ModuleInitializationLevel p_level) { - if (p_level!=MODULE_INITIALIZATION_LEVEL_SCENE) { - return; + if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) { + memdelete(srv); } } void register_steam_audio_settings() { - ProjectSettings *settings = ProjectSettings::get_singleton(); + ProjectSettings *settings = ProjectSettings::get_singleton(); {//Raytracer enum settings->set("steam_audio/ray_tracer/RayTracer",0); Dictionary info; @@ -33,7 +40,7 @@ void register_steam_audio_settings() { info["hint_string"] = "Steam RT,Embree RT"; info["usage"] = PROPERTY_USAGE_DEFAULT; settings->add_property_info(info); - settings->set("steam_audio/ray_tracer/RayTracer",1); + settings->set_initial_value("steam_audio/ray_tracer/RayTracer",1); } {//spatialization mode enum @@ -49,7 +56,31 @@ void register_steam_audio_settings() { } { - settings->set("steam_audio/spatializer/HRTF/Volume",0); + settings->set("steam_audio/spatializer/HRTF/HRTF_Type",0); + Dictionary info; + info["name"] = "steam_audio/spatializer/HRTF/HRTF_Type"; + info["type"] = Variant::INT; + info["hint"] = PROPERTY_HINT_ENUM; + info["hint_string"] = "Default,Custom"; + info["usage"] = PROPERTY_USAGE_DEFAULT; + settings->add_property_info(info); + settings->set_initial_value("steam_audio/spatializer/HRTF/HRTF_Type",0); + } + + { + settings->set("steam_audio/spatializer/HRTF/Sofa_File",0.0f); + Dictionary info; + info["name"] = "steam_audio/spatializer/HRTF/Sofa_File"; + info["type"] = Variant::STRING; + info["hint"] = PROPERTY_HINT_FILE; + info["hint_string"] = ""; + info["usage"] = PROPERTY_USAGE_DEFAULT; + settings->add_property_info(info); + settings->set_initial_value("steam_audio/spatializer/HRTF/Sofa_File",""); + } + + { + settings->set("steam_audio/spatializer/HRTF/Volume",1.0f); Dictionary info; info["name"] = "steam_audio/spatializer/HRTF/Volume"; info["type"] = Variant::FLOAT; @@ -57,9 +88,42 @@ void register_steam_audio_settings() { info["hint_string"] = "0.0,1.0,.01,slider"; info["usage"] = PROPERTY_USAGE_DEFAULT; settings->add_property_info(info); - settings->set("steam_audio/spatializer/HRTF/Volume",1); + settings->set_initial_value("steam_audio/spatializer/HRTF/Volume",1.0f); } + { + settings->set("steam_audio/spatializer/HRTF/Norm_Type",0); + Dictionary info; + info["name"] = "steam_audio/spatializer/HRTF/Norm_Type"; + info["type"] = Variant::INT; + info["hint"] = PROPERTY_HINT_ENUM; + info["hint_string"] = "NONE,Root Mean Squared"; + info["usage"] = PROPERTY_USAGE_DEFAULT; + settings->add_property_info(info); + settings->set_initial_value("steam_audio/spatializer/HRTF/Norm_Type",0); + } + + { + settings->set("steam_audio/sampling_rate",0); + Dictionary info; + info["name"] = "steam_audio/sampling_rate"; + info["type"] = Variant::INT; + info["hint"] = PROPERTY_HINT_ENUM; + info["hint_string"] = "44100:44100,48000:48000"; + settings->add_property_info(info); + settings->set_initial_value("steam_audio/sampling_rate",48000); + } + + { + settings->set("steam_audio/buffer_size",0); + Dictionary info; + info["name"] = "steam_audio/buffer_size"; + info["type"] = Variant::INT; + info["hint"] = PROPERTY_HINT_ENUM; + info["hint_string"] = "512:512,1024:1024"; + settings->add_property_info(info); + settings->set_initial_value("steam_audio/buffer_size",1024); + } } extern "C" { diff --git a/src/register_types.h b/src/register_types.hpp similarity index 61% rename from src/register_types.h rename to src/register_types.hpp index 914b7ac..f0d916c 100644 --- a/src/register_types.h +++ b/src/register_types.hpp @@ -3,12 +3,13 @@ #include #include -#include "steam_audio.h" -#include "steam_audio_listener.h" -#include "steam_audio_material.h" -#include "steam_audio_source.h" -#include "steam_audio_static_mesh.h" -#include "steam_audio_dynamic_mesh.h" +#include "steam_audio.hpp" +#include "steam_audio_dynamic_mesh.hpp" +#include "steam_audio_source.hpp" +#include "steam_audio_listener.hpp" +#include "steam_audio_server.hpp" +#include "steam_audio_material.hpp" +#include "steam_audio_static_mesh.hpp" using namespace godot; diff --git a/src/steam_audio.cpp b/src/steam_audio.cpp index 3eb6cc4..e976daf 100644 --- a/src/steam_audio.cpp +++ b/src/steam_audio.cpp @@ -1,118 +1,32 @@ -#include "steam_audio.h" +#include "steam_audio.hpp" using namespace godot; -IPLContext global_context = nullptr; -IPLScene global_scene = nullptr; -IPLSimulator global_simulator = nullptr; -SteamAudio::SteamAudio() { - steam_audio=this; - initialize(); + SteamAudio::SteamAudio() { } - SteamAudio::~SteamAudio() -{ - steam_audio = nullptr; - shutdown(); -} - void SteamAudio::_bind_methods() { - ClassDB::bind_method(D_METHOD("initialize"), &SteamAudio::initialize); - ClassDB::bind_method(D_METHOD("shutdown"), &SteamAudio::shutdown); - - ClassDB::bind_method(D_METHOD("get_hrtf"), &SteamAudio::get_hrtf); - ClassDB::bind_method(D_METHOD("get_hrtf_settings"), &SteamAudio::get_hrtf_settings); - - ClassDB::bind_method(D_METHOD("get_simulator"), &SteamAudio::get_simulator); - ClassDB::bind_method(D_METHOD("get_simulation_settings"), &SteamAudio::get_simulation_settings); - - ClassDB::bind_method(D_METHOD("get_context"),&SteamAudio::get_context); - ClassDB::bind_method(D_METHOD("get_context_settings"),&SteamAudio::get_context_settings); - - ClassDB::bind_method(D_METHOD("get_scene"), &SteamAudio::get_scene); - ClassDB::bind_method(D_METHOD("get_scene_settings"), &SteamAudio::get_scene_settings); - - ClassDB::bind_method(D_METHOD("get_embree_device"), &SteamAudio::get_embree_device); - ClassDB::bind_method(D_METHOD("get_embree_device_settings"),&SteamAudio::get_embree_device_settings); -} - -bool SteamAudio::initialize() { - ctx_settings.version=STEAMAUDIO_VERSION; - IPLContext ctx = nullptr; - iplContextCreate(&ctx_settings, &ctx); - iplSceneCreate(ctx,&scene_settings,&scene); - iplSimulatorCreate(ctx,&simulation_settings,&simulator); - int ray_mode = proj_settings->get_setting("steam_audio/ray_tracer"); - switch (ray_mode) { - case 0://steam rt - break; - case 1: // embree rt - iplEmbreeDeviceCreate(ctx,&embree_device_settings,&embree_device); - break; - default: - ERR_PRINT("Unknown Raytracer"); - return false; - } - int spat_mode = proj_settings->get_setting("steam_audio/spatializer"); - switch (spat_mode) { - case 0://panning - break; - case 1://HRTF - iplHRTFCreate(ctx,&audio_settings,&hrtf_settings,&hrtf); - break; - case 2://ambisonic pan - break; - case 3://ambisonic binaural - break; - default: - ERR_PRINT("Unknown Spatializer"); - return false; - } - print_line("Steam Audio Successfully initialized"); - - return true; -} - -void SteamAudio::update_static_scene() { - -} - -void SteamAudio::update_dynamic_scene() { - -} - - -void SteamAudio::shutdown() { - if (embree_device != nullptr) { - iplEmbreeDeviceRelease(&embree_device); - embree_device = nullptr; - } - if (ctx != nullptr) { - iplContextRelease(&ctx); - ctx = nullptr; - } - if (hrtf != nullptr) { - iplHRTFRelease(&hrtf); - hrtf = nullptr; - } - if (simulator != nullptr) { - iplSimulatorRelease(&simulator); - simulator = nullptr; - } - if (scene!= nullptr) { - iplSceneRelease(&scene); - scene = nullptr; - } -} -bool SteamAudio::build_scene() { - update_dynamic_scene(); - update_static_scene(); -} -Array SteamAudio::get_nodes_with_child() { } +IPLContextSettings SteamAudio::ctx_default_settings = { + STEAMAUDIO_VERSION, + nullptr, + nullptr, + nullptr, + IPL_SIMDLEVEL_NEON, +}; +IPLSimulationSettings SteamAudio::simulation_default_settings = { +}; +IPLAudioSettings SteamAudio::audio_default_settings = { +}; +IPLSceneSettings SteamAudio::scene_default_settings = { +}; +IPLEmbreeDeviceSettings SteamAudio::embree_device_default_settings = { +}; +IPLHRTFSettings SteamAudio::hrtf_default_settings = { +}; IPLCoordinateSpace3 SteamAudio::godot_to_ipl_space(const Transform3D &t) { IPLCoordinateSpace3 s{}; @@ -129,7 +43,6 @@ IPLCoordinateSpace3 SteamAudio::godot_to_ipl_space(const Transform3D &t) { return s; } - // Steam Audio → Godot space Transform3D SteamAudio::ipl_space_to_godot(const IPLCoordinateSpace3 &s) { // construct a Basis from column vectors @@ -142,10 +55,45 @@ Transform3D SteamAudio::ipl_space_to_godot(const IPLCoordinateSpace3 &s) { return Transform3D(b, Vector3(s.origin.x, s.origin.y, s.origin.z)); } -void SteamAudio::_ready() { - reflections_thread; - build_scene(); - iplSimulatorRunDirect(simulator); +IPLMatrix4x4 SteamAudio::transform_to_ipl_matrix(const Transform3D &t) { + // First compute the three axes using Godot’s forward = –Z + Vector3 right = t.basis.xform(Vector3(1, 0, 0)); + Vector3 up = t.basis.xform(Vector3(0, 1, 0)); + Vector3 forward = t.basis.xform(Vector3(0, 0, -1)); // ← same as .ahead above + IPLMatrix4x4 m{}; + // Row-major: m[row][col] + + // X axis → column 0 + m.elements[0][0] = right.x; m.elements[1][0] = right.y; m.elements[2][0] = right.z; + + // Y axis → column 1 + m.elements[0][1] = up.x; m.elements[1][1] = up.y; m.elements[2][1] = up.z; + + // Forward (–Z) → column 2 + m.elements[0][2] = forward.x; m.elements[1][2] = forward.y; m.elements[2][2] = forward.z; + + // Translation → column 3 + m.elements[0][3] = t.origin.x; m.elements[1][3] = t.origin.y; m.elements[2][3] = t.origin.z; + + // Bottom row for homogeneous + m.elements[3][0] = 0; m.elements[3][1] = 0; m.elements[3][2] = 0; m.elements[3][3] = 1; + + return m; } +IPLMaterial SteamAudio::to_ipl_material(const SteamMaterial &material) { + IPLMaterial iplmaterial ={}; + + iplmaterial.absorption[0] = material.absorption.x; + iplmaterial.absorption[1] = material.absorption.y; + iplmaterial.absorption[2] = material.absorption.z; + + iplmaterial.scattering = material.scattering; + + iplmaterial.transmission[0] = material.transmission.x; + iplmaterial.transmission[1] = material.transmission.y; + iplmaterial.transmission[2] = material.transmission.z; + + return iplmaterial; +} diff --git a/src/steam_audio.h b/src/steam_audio.h deleted file mode 100644 index c07d078..0000000 --- a/src/steam_audio.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "steam_audio_dynamic_mesh.h" -#include "steam_audio_listener.h" -#include "steam_audio_material.h" -#include "steam_audio_source.h" -#include "steam_audio_static_mesh.h" - - -using namespace godot; - -class SteamAudio : public Node -{ - GDCLASS( SteamAudio, Node ) // NOLINT(modernize-use-auto, hicpp-use-auto) - -private: - IPLContextSettings ctx_settings{}; - IPLAudioSettings audio_settings{}; - IPLContext ctx = nullptr; - IPLEmbreeDeviceSettings embree_device_settings{}; - IPLEmbreeDevice embree_device = nullptr; - IPLScene scene = nullptr; - IPLSceneSettings scene_settings{}; - IPLSimulationSettings simulation_settings{}; - IPLSimulator simulator = nullptr; - IPLStaticMesh mesh = nullptr; - IPLHRTFSettings hrtf_settings{}; - IPLHRTF hrtf = nullptr; - ProjectSettings *proj_settings = ProjectSettings::get_singleton(); - Array dynamic_geometry; - Array static_geometry; - Array sources; - Ref reflections_thread; - Ref pathing_thread; -protected: - static void _bind_methods(); - void _ready() override; -public: - static SteamAudio *steam_audio; - SteamAudio(); - ~SteamAudio() override; - bool initialize(); - void shutdown(); - - bool build_scene(); - - void update_static_scene(); - void update_dynamic_scene(); - - static Transform3D ipl_space_to_godot(const IPLCoordinateSpace3 &p_space); - static IPLCoordinateSpace3 godot_to_ipl_space(const Transform3D &p_transform); - - [[nodiscard]] IPLContext get_context() const{return ctx;} - - [[nodiscard]] IPLEmbreeDevice get_embree_device() const{return embree_device;} - [[nodiscard]] IPLEmbreeDeviceSettings get_embree_device_settings() const{return embree_device_settings;} - - [[nodiscard]] IPLScene get_scene() const{return scene;} - [[nodiscard]] IPLSceneSettings get_scene_settings() const{return scene_settings;} - - [[nodiscard]] IPLSimulator get_simulator() const{return simulator;} - [[nodiscard]] IPLSimulationSettings get_simulation_settings() const{return simulation_settings;} - - [[nodiscard]] IPLHRTF get_hrtf() const{return hrtf;} - [[nodiscard]] IPLHRTFSettings get_hrtf_settings() const{return hrtf_settings;} - - [[nodiscard]] IPLContextSettings get_context_settings() const{return ctx_settings;} - -}; \ No newline at end of file diff --git a/src/steam_audio.hpp b/src/steam_audio.hpp new file mode 100644 index 0000000..fcfa695 --- /dev/null +++ b/src/steam_audio.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "steam_audio_listener.hpp" +#include "steam_audio_material.hpp" + +using namespace godot; + +enum ProxyMode { + PROXY_NONE = 0, + PROXY_AUTO = 1, + PROXY_CUSTOM = 2, +}; + +class SteamAudio : public Resource +{ + GDCLASS( SteamAudio, Resource ) // NOLINT(modernize-use-auto, hicpp-use-auto) + +private: + static IPLContextSettings ctx_default_settings; + static IPLAudioSettings audio_default_settings; + static IPLEmbreeDeviceSettings embree_device_default_settings; + static IPLSceneSettings scene_default_settings; + static IPLSimulationSettings simulation_default_settings; + static IPLHRTFSettings hrtf_default_settings; + ProjectSettings *proj_settings = ProjectSettings::get_singleton(); + +protected: + static void _bind_methods(); + +public: + static SteamAudio *steam_audio; + + SteamAudio(); + ~SteamAudio() override = default; + + static Transform3D ipl_space_to_godot(const IPLCoordinateSpace3 &p_space); + static IPLMatrix4x4 transform_to_ipl_matrix(const Transform3D &t); + static IPLMaterial to_ipl_material(const SteamMaterial &); + + static IPLCoordinateSpace3 godot_to_ipl_space(const Transform3D &p_transform); + + [[nodiscard]] static IPLEmbreeDeviceSettings get_embree_device_settings() {return embree_device_default_settings;} + + [[nodiscard]] static IPLSceneSettings get_scene_settings() {return scene_default_settings;} + + [[nodiscard]] static IPLSimulationSettings get_simulation_settings() {return simulation_default_settings;} + + [[nodiscard]] static IPLHRTFSettings get_hrtf_settings() {return hrtf_default_settings;} + + [[nodiscard]] static IPLContextSettings get_context_settings() {return ctx_default_settings;} + +}; \ No newline at end of file diff --git a/src/steam_audio_dynamic_mesh.cpp b/src/steam_audio_dynamic_mesh.cpp index f84184e..16b018c 100644 --- a/src/steam_audio_dynamic_mesh.cpp +++ b/src/steam_audio_dynamic_mesh.cpp @@ -1,92 +1,283 @@ -#include "steam_audio_dynamic_mesh.h" -#include "steam_audio_globals.h" // holds global_context & global_scene +// steam_audio_static_mesh.cpp +#include "steam_audio_dynamic_mesh.hpp" using namespace godot; -SteamAudioDynamicMesh::SteamAudioDynamicMesh() {} -SteamAudioDynamicMesh::~SteamAudioDynamicMesh() { - if (instanced_mesh) { - // remove & release the instance - iplInstancedMeshRemove(instanced_mesh, global_scene); - iplInstancedMeshRelease(&instanced_mesh); - } - if (proxy_subscene) { - iplSceneRelease(&proxy_subscene); - } +Vector SteamAudioDynamicMesh::_instances; + +SteamAudioDynamicMesh::SteamAudioDynamicMesh() { + _instances.push_back(this); } +SteamAudioDynamicMesh::~SteamAudioDynamicMesh() { + _instances.erase(this); +} + +const Vector &SteamAudioDynamicMesh::get_all_dynamic_meshes() { + return _instances; +} + + void SteamAudioDynamicMesh::_bind_methods() { - // no exposed properties for now + ClassDB::bind_method(D_METHOD("set_proxy_mode", "mode"), &SteamAudioDynamicMesh::set_proxy_mode); + ClassDB::bind_method(D_METHOD("get_proxy_mode"), &SteamAudioDynamicMesh::get_proxy_mode); + ADD_PROPERTY( + PropertyInfo(Variant::INT, "proxy_mode",PROPERTY_HINT_ENUM,"None,Convex,Custom"), "set_proxy_mode","get_proxy_mode"); + + ClassDB::bind_method(D_METHOD("set_custom_proxy_mesh", "mesh"), &SteamAudioDynamicMesh::set_custom_proxy_mesh); + ClassDB::bind_method(D_METHOD("get_custom_proxy_mesh"), &SteamAudioDynamicMesh::get_custom_proxy_mesh); + ADD_PROPERTY( + PropertyInfo(Variant::OBJECT, "custom_proxy_mesh",PROPERTY_HINT_RESOURCE_TYPE,"Mesh"), "set_custom_proxy_mesh","get_custom_proxy_mesh"); } -void SteamAudioDynamicMesh::_ready() { - build_subscene(); - - // instance it in the main scene, with initial transform - IPLInstancedMeshSettings ims{}; - ims.subScene = proxy_subscene; - ims.transform = to_ipl_matrix(get_global_transform()); - - iplInstancedMeshCreate(global_scene, &ims, &instanced_mesh); - iplInstancedMeshAdd(instanced_mesh, global_scene); - iplSceneCommit(global_scene); +// Setters & getters --------------------------------------------------- +void SteamAudioDynamicMesh::set_proxy_mode(int mode) { + proxy_mode = mode; } -void SteamAudioDynamicMesh::_process(double /*delta*/) { - if (!instanced_mesh) return; - - // update the world‐space transform - IPLMatrix4x4 mat = to_ipl_matrix(get_global_transform()); - iplInstancedMeshUpdateTransform(instanced_mesh, global_scene, mat); - - // commit once per frame (best performance if after all updates) :contentReference[oaicite:0]{index=0} - iplSceneCommit(global_scene); +int SteamAudioDynamicMesh::get_proxy_mode() const { + return proxy_mode; } -void SteamAudioDynamicMesh::build_subscene() { - // 1) create a fresh sub-scene - IPLSceneSettings ss{}; - iplSceneCreate(global_context, &ss, &proxy_subscene); - - // 2) pull raw verts/indices out of this MeshInstance3D - Ref mesh = cast_to(get_parent())->get_mesh(); - if (mesh.is_null()) return; - - // for simplicity, we assume a single surface: - Array arr = mesh->surface_get_arrays(0); - PackedVector3Array verts = arr[Mesh::ARRAY_VERTEX]; - PackedInt32Array idxs = arr[Mesh::ARRAY_INDEX]; - - // fill out static‐mesh settings - IPLStaticMeshSettings sms{}; - sms.numVertices = verts.size(); - sms.vertices = reinterpret_cast(const_cast(verts.ptr())); - sms.numTriangles = idxs.size() / 3; - // convert PoolIntArray → IPLTriangle* - // …you’d build & fill an IPLTriangle array here… - // sms.triangles = your_triangle_buffer; - - // add it to the proxy_subscene - IPLStaticMesh sm = nullptr; - iplStaticMeshCreate(proxy_subscene, &sms, &sm); - iplStaticMeshAdd(sm, proxy_subscene); - - // finally commit the sub-scene so it’s ready to be instanced :contentReference[oaicite:1]{index=1} - iplSceneCommit(proxy_subscene); +void SteamAudioDynamicMesh::set_custom_proxy_mesh(Ref mesh) { + custom_proxy_mesh = mesh; } -IPLMatrix4x4 SteamAudioDynamicMesh::to_ipl_matrix(const Transform3D &t) { - // row-major: each m[i][j] is row i, column j - IPLMatrix4x4 m{}; - // basis X column - m.elements[0][0] = t.basis[0][0]; m.elements[1][0] = t.basis[0][1]; m.elements[2][0] = t.basis[0][2]; - // basis Y column - m.elements[0][1] = t.basis[1][0]; m.elements[1][1] = t.basis[1][1]; m.elements[2][1] = t.basis[1][2]; - // basis Z column - m.elements[0][2] = t.basis[2][0]; m.elements[1][2] = t.basis[2][1]; m.elements[2][2] = t.basis[2][2]; - // origin - m.elements[0][3] = t.origin.x; m.elements[1][3] = t.origin.y; m.elements[2][3] = t.origin.z; - // bottom row - m.elements[3][0] = 0; m.elements[3][1] = 0; m.elements[3][2] = 0; m.elements[3][3] = 1; - return m; +Ref SteamAudioDynamicMesh::get_custom_proxy_mesh() const { + return custom_proxy_mesh; +} + +void SteamAudioDynamicMesh::_notification(int what) { + Transform3D current; + switch (what) { + case NOTIFICATION_ENTER_TREE: + needs_update=true; + break; + case NOTIFICATION_EXIT_TREE: + break; + case NOTIFICATION_TRANSFORM_CHANGED: + current = get_global_transform(); + if (!last_transform.is_equal_approx(current)) { + last_transform = current; + needs_update=true; + } + default: + break; + } +} + + // Core functionality -------------------------------------------------- +Ref SteamAudioDynamicMesh::get_proxy_mesh() { + print_line("getting the proxy mesh"); + print_line(vformat("proxy_mode: %f", proxy_mode)); + + MeshInstance3D *parent = cast_to(get_parent()); + if (!parent) { + print_error("Parent is not a MeshInstance3D!"); + return {}; + } + Ref target_mesh; + Ref source_mesh = parent->get_mesh(); + if (!source_mesh.is_valid()) { + print_error("Source mesh is invalid!"); + return {}; + } + + print_line("using parent mesh"); + print_line(vformat("Source mesh type: %s", source_mesh->get_class())); + + switch (proxy_mode) { + case PROXY_NONE: + target_mesh = source_mesh; + break; + case PROXY_AUTO: + + break; + + case PROXY_CUSTOM: + + break; + default: + print_error("Unknown proxy mode!"); + return {}; + } + return target_mesh; +} +void SteamAudioDynamicMesh::update_dynamic_mesh() { + IPLMatrix4x4 transform = SteamAudio::transform_to_ipl_matrix(get_global_transform()); + iplInstancedMeshUpdateTransform(instanced_mesh,global_scene,transform); +} + + +void SteamAudioDynamicMesh::build_mesh(IPLScene scene,IPLContext ipl_context,IPLContextSettings ipl_context_settings) { + global_scene=scene; + iplSceneCreate(ipl_context,&scene_settings,&subscene); + if (!scene) { + ERR_PRINT("Invalid IPLScene passed to build_mesh"); + return; + } + + struct SurfaceData { + PackedVector3Array vertices; + PackedInt32Array indices; + IPLMaterial material; + }; + + Vector surface_data; + print_line("building a static mesh"); + Ref mesh = get_proxy_mesh(); + + if (mesh.is_null()) { + ERR_PRINT("Failed to get valid proxy mesh"); + return; + } + print_line("got the mesh"); + + for (int surface_idx = 0; surface_idx < mesh->get_surface_count(); surface_idx++) { + SurfaceData data; + Array arrays = mesh->surface_get_arrays(surface_idx); + + if (arrays.is_empty()) { + ERR_PRINT(vformat("Surface %d has no arrays data", surface_idx)); + continue; + } + + // Get vertices and indices + PackedVector3Array vertices = arrays[Mesh::ARRAY_VERTEX]; + PackedInt32Array indices = arrays[Mesh::ARRAY_INDEX]; + + if (vertices.is_empty()) { + ERR_PRINT(vformat("Surface %d has no vertices", surface_idx)); + continue; + } + + // If indices array is empty, create one from vertices + if (indices.is_empty()) { + indices.resize(vertices.size()); + for (int i = 0; i < vertices.size(); i++) { + indices[i] = i; + } + } + + // Create triangles if indices count is not a multiple of 3 + if (indices.size() % 3 != 0) { + print_line("Surface " + itos(surface_idx) + " is not triangulated, converting to triangles"); + PackedInt32Array new_indices; + + // Simple triangulation for convex polygons + for (int i = 0; i < indices.size() - 2; i++) { + new_indices.push_back(indices[0]); + new_indices.push_back(indices[i + 1]); + new_indices.push_back(indices[i + 2]); + } + + indices = new_indices; + } + + data.vertices = vertices; + data.indices = indices; + + // Get material for the surface + Ref material = mesh->surface_get_material(surface_idx); + if (material.is_valid() && material->has_meta("steam_audio_material")) { + Ref steam_audio_material = material->get_meta("steam_audio_material"); + if (steam_audio_material.is_valid()) { + SteamMaterial steam_material = steam_audio_material->get_steam_material(); + data.material = SteamAudio::to_ipl_material(steam_material); + } else { + WARN_PRINT(vformat("Invalid Steam Audio material for surface %d", surface_idx)); + data.material = SteamAudio::to_ipl_material(SteamAudioMaterial::get_default_material()); + } + } else { + data.material = SteamAudio::to_ipl_material(SteamAudioMaterial::get_default_material()); + } + + surface_data.push_back(data); + } + + if (surface_data.is_empty()) { + ERR_PRINT("No valid surfaces found in mesh"); + return; + } + + // Calculate total counts + int total_vertices = 0; + int total_triangles = 0; + for (const SurfaceData &data: surface_data) { + total_vertices += data.vertices.size(); + total_triangles += data.indices.size() / 3; + } + + if (total_vertices == 0 || total_triangles == 0) { + ERR_PRINT("Mesh has no vertices or triangles"); + return; + } + + // Prepare final data arrays + PackedFloat32Array final_vertices; + PackedInt32Array final_triangles; + Vector final_materials; + PackedInt32Array material_indices; + + final_vertices.resize(total_vertices * 3); + final_triangles.resize(total_triangles * 3); + material_indices.resize(total_triangles); + + // Combine all surfaces + int vertex_offset = 0; + int triangle_offset = 0; + int material_index = 0; + + for (const SurfaceData &data: surface_data) { + // Copy vertices + for (int i = 0; i < data.vertices.size(); i++) { + const Vector3 &v = data.vertices[i]; + final_vertices[vertex_offset * 3 + 0] = v.x; + final_vertices[vertex_offset * 3 + 1] = v.y; + final_vertices[vertex_offset * 3 + 2] = v.z; + vertex_offset++; + } + + // Copy triangles + int base_vertex = vertex_offset - data.vertices.size(); + for (int i = 0; i < data.indices.size(); i += 3) { + final_triangles[triangle_offset * 3 + 0] = base_vertex + data.indices[i + 0]; + final_triangles[triangle_offset * 3 + 1] = base_vertex + data.indices[i + 1]; + final_triangles[triangle_offset * 3 + 2] = base_vertex + data.indices[i + 2]; + + material_indices[triangle_offset] = material_index; + triangle_offset++; + } + + final_materials.push_back(data.material); + material_index++; + } + + // Create Steam Audio static mesh + IPLStaticMeshSettings mesh_settings = {}; + mesh_settings.numVertices = total_vertices; + mesh_settings.numTriangles = total_triangles; + mesh_settings.numMaterials = final_materials.size(); + mesh_settings.vertices = const_cast(reinterpret_cast(final_vertices.ptr())); + mesh_settings.triangles = const_cast(reinterpret_cast(final_triangles.ptr())); + mesh_settings.materials = const_cast(final_materials.ptr()); + mesh_settings.materialIndices = const_cast(material_indices.ptr()); + + IPLStaticMesh static_mesh; + IPLerror error = iplStaticMeshCreate(subscene, &mesh_settings, &static_mesh); + if (error != IPL_STATUS_SUCCESS) { + ERR_PRINT(vformat("Failed to create static mesh. Error code: %d", error)); + return; + } + + print_line(vformat("Successfully created static mesh with %d vertices and %d triangles", total_vertices, + total_triangles)); + instanced_mesh_settings.subScene = subscene; + instanced_mesh_settings.transform = SteamAudio::transform_to_ipl_matrix(get_global_transform()); + + IPLerror imErr = iplInstancedMeshCreate(global_scene,&instanced_mesh_settings,&instanced_mesh); + if (imErr != IPL_STATUS_SUCCESS) { + ERR_PRINT(vformat("Failed to create instanced mesh. Error code: %d", imErr)); + return; + } } diff --git a/src/steam_audio_dynamic_mesh.h b/src/steam_audio_dynamic_mesh.h deleted file mode 100644 index 675a125..0000000 --- a/src/steam_audio_dynamic_mesh.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#include - -using namespace godot; - -class SteamAudioDynamicMesh : public Node3D { - GDCLASS(SteamAudioDynamicMesh, Node3D) - -private: - IPLScene proxy_subscene = nullptr; - IPLInstancedMesh instanced_mesh = nullptr; - - // helper: convert Godot Transform3D → IPLMatrix4x4 - static IPLMatrix4x4 to_ipl_matrix(const Transform3D &t); - - // builds a one-off IPLScene containing this mesh’s raw geometry - void build_subscene(); - -public: - SteamAudioDynamicMesh(); - ~SteamAudioDynamicMesh() override; - - void _ready() override; - void _process(double delta) override; - -protected: - static void _bind_methods(); -}; \ No newline at end of file diff --git a/src/steam_audio_dynamic_mesh.hpp b/src/steam_audio_dynamic_mesh.hpp new file mode 100644 index 0000000..b425240 --- /dev/null +++ b/src/steam_audio_dynamic_mesh.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // IPLStaticMesh, IPLStaticMeshSettings, IPLMaterial +#include "steam_audio.hpp" +#include "steam_audio_material.hpp" +#include + +using namespace godot; + +class SteamAudioDynamicMesh : public Node3D { + GDCLASS(SteamAudioDynamicMesh, Node3D) + +private: + IPLScene subscene=nullptr; + IPLScene global_scene=nullptr; + IPLSceneSettings scene_settings; + IPLInstancedMesh instanced_mesh=nullptr; + IPLInstancedMeshSettings instanced_mesh_settings; + int proxy_mode = PROXY_NONE; + Ref custom_proxy_mesh; + + Transform3D last_transform; + StringName bus; + + bool needs_update = false; + +protected: + static void _bind_methods(); + + +public: + SteamAudioDynamicMesh(); + ~SteamAudioDynamicMesh() override; + + void _notification(int what); + + // Setters & getters + void set_proxy_mode(int mode); + int get_proxy_mode() const; + + void set_custom_proxy_mesh(Ref mesh); + Ref get_custom_proxy_mesh() const; + + bool get_needs_update() { + return needs_update; + } + void build_mesh(IPLScene scene, IPLContext ipl_context, IPLContextSettings ipl_context_settings); + static Vector _instances; + static const Vector& get_all_dynamic_meshes(); + + // Regenerates Steam Audio proxies + Ref get_proxy_mesh(); + + void update_dynamic_mesh(); +}; diff --git a/src/steam_audio_globals.h b/src/steam_audio_globals.h deleted file mode 100644 index 0a9ee6c..0000000 --- a/src/steam_audio_globals.h +++ /dev/null @@ -1,7 +0,0 @@ -// steam_audio_globals.h -#pragma once -#include -#include -extern IPLContext global_context; -extern IPLScene global_scene; -extern IPLSimulator global_simulator; \ No newline at end of file diff --git a/src/steam_audio_listener.cpp b/src/steam_audio_listener.cpp index fdfe5af..e3b0c9f 100644 --- a/src/steam_audio_listener.cpp +++ b/src/steam_audio_listener.cpp @@ -1,46 +1,52 @@ -#include "steam_audio_listener.h" - -#include "phonon.h" +#include "steam_audio_listener.hpp" using namespace godot; -SteamAudioListener::SteamAudioListener() = default; -SteamAudioListener::~SteamAudioListener() = default; +SteamAudioListener* SteamAudioListener::listener; + +SteamAudioListener::SteamAudioListener() { + listener=this; +} + +SteamAudioListener::~SteamAudioListener() { + listener=nullptr; +} void SteamAudioListener::_bind_methods() { - } -void SteamAudioListener::_process(double delta) { +SteamAudioListener* SteamAudioListener::get_listener() { + return listener; +} + +void SteamAudioListener::_notification(int p_what) { + Transform3D current_transform; + switch (p_what) { + case NOTIFICATION_TRANSFORM_CHANGED: + current_transform = get_global_transform(); + if (!last_transform.is_equal_approx(current_transform)) { + needs_update = true; + last_transform = current_transform; + } + break; + case NOTIFICATION_ENTER_TREE: + needs_update = true; + break; + default: + break; + } +} + +void SteamAudioListener::update_listener(IPLSimulator iplsim, IPLSimulationSharedInputs iplsiminputs) { + needs_update=false; + Transform3D transform = get_global_transform(); - IPLVector3 pos = { - static_cast(transform.origin.x), - static_cast(transform.origin.y), - static_cast(transform.origin.z) - }; - Vector3 godot_fwd = transform.basis.xform(Vector3(0, 0, -1)); - IPLVector3 fwd = { - static_cast(godot_fwd.x), - static_cast(godot_fwd.y), - static_cast(godot_fwd.z) - }; - Vector3 godot_up = transform.basis.xform(Vector3(0, 1, 0)); - IPLVector3 up = { - static_cast(godot_up.x), - static_cast(godot_up.y), - static_cast(godot_up.z) - }; - IPLVector3 right = { - fwd.y * up.z - fwd.z * up.y, - fwd.z * up.x - fwd.x * up.z, - fwd.x * up.y - fwd.y * up.x - }; - IPLCoordinateSpace3 listenerCS{}; - listenerCS.origin = pos; - listenerCS.ahead = fwd; - listenerCS.up = up; - listenerCS.right = right; - IPLSimulationSharedInputs sharedInputs{}; - sharedInputs.listener = listenerCS; -} + IPLCoordinateSpace3 space = SteamAudio::godot_to_ipl_space(transform); + + iplsiminputs.listener=space; + + iplSimulatorSetSharedInputs(iplsim,IPL_SIMULATIONFLAGS_DIRECT,&iplsiminputs); + iplSimulatorSetSharedInputs(iplsim,IPL_SIMULATIONFLAGS_PATHING,&iplsiminputs); + iplSimulatorSetSharedInputs(iplsim,IPL_SIMULATIONFLAGS_REFLECTIONS,&iplsiminputs); +} \ No newline at end of file diff --git a/src/steam_audio_listener.h b/src/steam_audio_listener.h deleted file mode 100644 index 53fb237..0000000 --- a/src/steam_audio_listener.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -using namespace godot; - -class SteamAudioListener:public AudioListener3D -{ - GDCLASS(SteamAudioListener,AudioListener3D) - - -protected: - static void _bind_methods(); - void _process(double p_delta) override; -public: - SteamAudioListener(); - ~SteamAudioListener() override; - -private: - -}; \ No newline at end of file diff --git a/src/steam_audio_listener.hpp b/src/steam_audio_listener.hpp new file mode 100644 index 0000000..5a53d3a --- /dev/null +++ b/src/steam_audio_listener.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include +#include "steam_audio.hpp" +#include "phonon.h" + +using namespace godot; + +class SteamAudioListener:public AudioListener3D +{ + GDCLASS(SteamAudioListener,AudioListener3D) +private: + bool needs_update = false; + Transform3D last_transform; + +protected: + static void _bind_methods(); +public: + bool get_needs_update() { + return needs_update; + } + static SteamAudioListener *listener; + SteamAudioListener(); + ~SteamAudioListener() override; + + void _notification(int p_what); + + static SteamAudioListener* get_listener(); + void update_listener(IPLSimulator iplsim, IPLSimulationSharedInputs iplsiminputs); + void set_listener_position(const Vector3 &p_position); + void set_listener_orientation(const Vector3 &p_forward, const Vector3 &p_up); + void set_listener_velocity(const Vector3 &p_velocity); + void set_listener_gain(const float p_gain); + void set_listener_doppler_factor(const float p_factor); + void set_listener_distance_model(const int p_model); + void set_listener_flags(const int p_flags); + void set_listener_channel_count(const int p_count); +}; \ No newline at end of file diff --git a/src/steam_audio_material.cpp b/src/steam_audio_material.cpp index 5b068ad..a79c0b2 100644 --- a/src/steam_audio_material.cpp +++ b/src/steam_audio_material.cpp @@ -1,4 +1,4 @@ -#include "steam_audio_material.h" +#include "steam_audio_material.hpp" using namespace godot; @@ -14,26 +14,22 @@ void SteamAudioMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT,"scattering"), "set_scattering", "get_scattering"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3,"transmission"), "set_transmission", "get_transmission"); } -Vector3 SteamAudioMaterial::get_absorption() const {return absorption;} -void SteamAudioMaterial::set_absorption(Vector3 value) { absorption = value; } -float SteamAudioMaterial::get_scattering() const {return scattering;} -void SteamAudioMaterial::set_scattering(float value) { scattering = value; } -Vector3 SteamAudioMaterial::get_transmission() const {return transmission;} -void SteamAudioMaterial::set_transmission(Vector3 value) { transmission = value; } -IPLMaterial SteamAudioMaterial::to_ipl_material() const { - IPLMaterial material ={}; - material.absorption[0] = absorption.x; - material.absorption[1] = absorption.y; - material.absorption[2] = absorption.z; +SteamMaterial SteamAudioMaterial::default_material = { + Vector3(0.10f,0.20f,0.30f), + 0.05f, + Vector3(0.100f,0.050f,0.030f) +}; - material.scattering = scattering; - - material.transmission[0] = transmission.x; - material.transmission[1] = transmission.y; - material.transmission[2] = transmission.z; - - return material; +Vector3 SteamAudioMaterial::get_absorption() const {return material.absorption;} +void SteamAudioMaterial::set_absorption(Vector3 value) { material.absorption = value; } +float SteamAudioMaterial::get_scattering() const {return material.scattering;} +void SteamAudioMaterial::set_scattering(float value) { material.scattering = value; } +Vector3 SteamAudioMaterial::get_transmission() const {return material.transmission;} +void SteamAudioMaterial::set_transmission(Vector3 value) { material.transmission = value; } +const SteamMaterial &SteamAudioMaterial::get_default_material() { + return default_material; } - - +SteamMaterial SteamAudioMaterial::get_steam_material() const { + return material; +} \ No newline at end of file diff --git a/src/steam_audio_material.h b/src/steam_audio_material.hpp similarity index 64% rename from src/steam_audio_material.h rename to src/steam_audio_material.hpp index 1c053a2..9a9d57e 100644 --- a/src/steam_audio_material.h +++ b/src/steam_audio_material.hpp @@ -1,27 +1,32 @@ #pragma once - +#include #include -#include "godot_cpp/godot.hpp" #include +#include "godot_cpp/classes/wrapped.hpp" +#include "godot_cpp/godot.hpp" #include "phonon.h" using namespace godot; - +struct SteamMaterial { + Vector3 absorption; + float scattering; + Vector3 transmission; +}; class SteamAudioMaterial : public Resource { GDCLASS(SteamAudioMaterial,Resource) // NOLINT(modernize-use-auto, hicpp-use-auto) protected: static void _bind_methods(); private: - Vector3 absorption = Vector3(0.2, 0.2, 0.2); - float scattering = 0.0; - Vector3 transmission = Vector3(0.0, 0.0, 0.0); + SteamMaterial material; + public: + static SteamMaterial default_material; + static const SteamMaterial& get_default_material(); void set_absorption(const Vector3 value); void set_scattering(const float value); void set_transmission(const Vector3 value); Vector3 get_absorption() const; [[nodiscard]] float get_scattering() const; Vector3 get_transmission() const; - - [[nodiscard]] IPLMaterial to_ipl_material() const; + SteamMaterial get_steam_material() const; }; \ No newline at end of file diff --git a/src/steam_audio_server.cpp b/src/steam_audio_server.cpp new file mode 100644 index 0000000..d5582fe --- /dev/null +++ b/src/steam_audio_server.cpp @@ -0,0 +1,16 @@ +#include "steam_audio_server.hpp" + +using namespace godot; + + SteamAudioServer::SteamAudioServer() { + +} + + SteamAudioServer::~SteamAudioServer() { + +} + +// Core Godot Methods +void SteamAudioServer::_bind_methods() { + +} diff --git a/src/steam_audio_server.hpp b/src/steam_audio_server.hpp new file mode 100644 index 0000000..71c4f86 --- /dev/null +++ b/src/steam_audio_server.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "steam_audio_dynamic_mesh.hpp" +#include "steam_audio_source.hpp" +#include "steam_audio_static_mesh.hpp" + +using namespace godot; + + +class SteamAudioServer : public Object { + GDCLASS(SteamAudioServer, Object) // Godot class declaration macro +private: + +public: + SteamAudioServer(); // Constructor + ~SteamAudioServer() override; // Destructor + +protected: + static void _bind_methods(); // Bind methods to Godot + +}; diff --git a/src/steam_audio_source.cpp b/src/steam_audio_source.cpp index c7a39cb..fb904d1 100644 --- a/src/steam_audio_source.cpp +++ b/src/steam_audio_source.cpp @@ -1,22 +1,51 @@ -#include "steam_audio_source.h" - -#include "steam_audio_globals.h" +#include "steam_audio_source.hpp" using namespace godot; + +// Static vector to track all active audio sources +Vector SteamAudioSource::_instances; + +// Constructor/Destructor SteamAudioSource::SteamAudioSource() { - iplSourceCreate(sa->get_simulator(),&settings,pSource); + // Disable default attenuation since Steam Audio handles it + set_attenuation_model(ATTENUATION_DISABLED); + _instances.push_back(this); } + SteamAudioSource::~SteamAudioSource() { - iplSourceRelease(pSource); + // Remove this instance from tracking vector + _instances.erase(this); } + +// Static method to get all active audio sources +const Vector &SteamAudioSource::get_all_sources() { + return _instances; +} + +// Godot virtual methods void SteamAudioSource::_bind_methods() { + // Bind directivity methods to make them accessible from GDScript + ClassDB::bind_method(D_METHOD("set_dipole_weight", "weight"), &SteamAudioSource::set_dipole_weight); + ClassDB::bind_method(D_METHOD("get_dipole_weight"), &SteamAudioSource::get_dipole_weight); + ClassDB::bind_method(D_METHOD("set_dipole_power", "power"), &SteamAudioSource::set_dipole_power); + ClassDB::bind_method(D_METHOD("get_dipole_power"), &SteamAudioSource::get_dipole_power); + ClassDB::bind_method(D_METHOD("set_directivity_enabled", "arg"), &SteamAudioSource::set_directivity_enabled); + ClassDB::bind_method(D_METHOD("is_directivity_enabled"), &SteamAudioSource::get_is_directivity_enabled); + + // Add properties to editor interface + ADD_GROUP("Directivity", "directivity_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directivity_enabled"), "set_directivity_enabled", + "is_directivity_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "directivity_dipole_weight", PROPERTY_HINT_RANGE, "0,1,0.01"), + "set_dipole_weight", "get_dipole_weight"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "directivity_dipole_power", PROPERTY_HINT_RANGE, "0,10,0.1"), + "set_dipole_power", "get_dipole_power"); } -void SteamAudioSource::_process(double delta) { - update_position(); -} -bool SteamAudioSource::update_position() { - IPLCoordinateSpace3 current_pos = sa->godot_to_ipl_space(get_transform()); - - iplSource -} +// Hide default AudioStreamPlayer3D properties that we don't use +void SteamAudioSource::_validate_property(PropertyInfo &property) const { + if (property.name == StringName("attenuation_model") || property.name == StringName("unit_size") || + property.name.begins_with("attenuation") || property.name.begins_with("emission_")) { + property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} \ No newline at end of file diff --git a/src/steam_audio_source.h b/src/steam_audio_source.h deleted file mode 100644 index eec3738..0000000 --- a/src/steam_audio_source.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include "steam_audio.h" -#include "phonon.h" - -using namespace godot; - -class SteamAudioSource: public Node3D -{ - GDCLASS(SteamAudioSource,Node3D) - - IPLSource *pSource = nullptr; - SteamAudio *sa = SteamAudio::steam_audio; - IPLSourceSettings settings{}; -protected: - static void _bind_methods(); - void _process(double p_delta) override; -public: - SteamAudioSource(); - ~SteamAudioSource(); - bool update_position(); - -}; \ No newline at end of file diff --git a/src/steam_audio_source.hpp b/src/steam_audio_source.hpp new file mode 100644 index 0000000..6ba4e84 --- /dev/null +++ b/src/steam_audio_source.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include "phonon.h" +#include +#include +#include +#include +#include "steam_audio.hpp" + + +using namespace godot; +class SteamAudioSource: public AudioStreamPlayer3D +{ + GDCLASS(SteamAudioSource,AudioStreamPlayer3D) // NOLINT(*-unhandled-self-assignment) + + AudioServer *audio_server = AudioServer::get_singleton(); + bool is_directivity_enabled = true; + float dipole_weight=0.0f; + float dipole_power=1.0f; + int mix_rate=0; + int buffer_size=0; + + IPLContext ipl_context = nullptr; + IPLSource *pSource = nullptr; + IPLSourceSettings settings{}; + IPLSimulationInputs inputs{}; + IPLDirectivity directivity{}; + + Transform3D last_transform; + bool needs_update = false; + + Ref playback; + Vector audio_buffer; +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; +public: + void _notification(int p_what); + void _process(double p_delta) override; + + SteamAudioSource(); + ~SteamAudioSource() override; + + void update_source(); + + void set_directivity_enabled(bool enabled){is_directivity_enabled=enabled;} + void set_dipole_weight(float w){dipole_weight=w;} + void set_dipole_power(float p){dipole_power=p;} + + [[nodiscard]] float get_dipole_weight() const{return dipole_weight;} + [[nodiscard]] float get_dipole_power() const{return dipole_power;} + [[nodiscard]] bool get_is_directivity_enabled() const{return is_directivity_enabled;} + + static Vector _instances; + static const Vector& get_all_sources(); + + [[nodiscard]] bool get_needs_update() const { + return needs_update;} +}; \ No newline at end of file diff --git a/src/steam_audio_static_mesh.cpp b/src/steam_audio_static_mesh.cpp index 89fd39c..8792423 100644 --- a/src/steam_audio_static_mesh.cpp +++ b/src/steam_audio_static_mesh.cpp @@ -1,25 +1,23 @@ // steam_audio_static_mesh.cpp -#include "steam_audio_static_mesh.h" - -#include -#include -#include -#include -#include -#include // IPLStaticMesh, IPLStaticMeshSettings, IPLMaterial +#include "steam_audio_static_mesh.hpp" using namespace godot; -SteamAudioStaticMesh::SteamAudioStaticMesh() = default; +Vector SteamAudioStaticMesh::_instances; + +SteamAudioStaticMesh::SteamAudioStaticMesh() { + _instances.push_back(this); +} SteamAudioStaticMesh::~SteamAudioStaticMesh() { - // release any previously created Steam Audio meshes - for (auto &sm : static_meshes) { - iplStaticMeshRelease(&sm); - } - static_meshes.clear(); + _instances.erase(this); } +const Vector &SteamAudioStaticMesh::get_all_static_meshes() { + return _instances; +} + + void SteamAudioStaticMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_proxy_mode", "mode"), &SteamAudioStaticMesh::set_proxy_mode); ClassDB::bind_method(D_METHOD("get_proxy_mode"), &SteamAudioStaticMesh::get_proxy_mode); @@ -30,19 +28,13 @@ void SteamAudioStaticMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_custom_proxy_mesh"), &SteamAudioStaticMesh::get_custom_proxy_mesh); ADD_PROPERTY( PropertyInfo(Variant::OBJECT, "custom_proxy_mesh",PROPERTY_HINT_RESOURCE_TYPE,"Mesh"), "set_custom_proxy_mesh","get_custom_proxy_mesh"); - - ClassDB::bind_method(D_METHOD("set_default_material", "mat"), &SteamAudioStaticMesh::set_default_material); - ClassDB::bind_method(D_METHOD("get_default_material"), &SteamAudioStaticMesh::get_default_material); - - // And still bind your proxy-generator: - ClassDB::bind_method(D_METHOD("generate_proxy_mesh"), &SteamAudioStaticMesh::generate_proxy_mesh); - } // Setters & getters --------------------------------------------------- void SteamAudioStaticMesh::set_proxy_mode(int mode) { proxy_mode = mode; } + int SteamAudioStaticMesh::get_proxy_mode() const { return proxy_mode; } @@ -50,132 +42,217 @@ int SteamAudioStaticMesh::get_proxy_mode() const { void SteamAudioStaticMesh::set_custom_proxy_mesh(Ref mesh) { custom_proxy_mesh = mesh; } + Ref SteamAudioStaticMesh::get_custom_proxy_mesh() const { return custom_proxy_mesh; } -void SteamAudioStaticMesh::set_default_material(Ref mat) { - default_material = mat; -} -Ref SteamAudioStaticMesh::get_default_material() const { - return default_material; -} - // Core functionality -------------------------------------------------- -void SteamAudioStaticMesh::generate_proxy_mesh() { - // 1) Clean up any old meshes - for (auto &sm : static_meshes) { - iplStaticMeshRelease(&sm); - } - static_meshes.clear(); +Ref SteamAudioStaticMesh::get_proxy_mesh() { + print_line("getting the proxy mesh"); + print_line(vformat("proxy_mode: %f", proxy_mode)); + + MeshInstance3D* parent = cast_to(get_parent()); + if (!parent) { + print_error("Parent is not a MeshInstance3D!"); + return {}; + } + Ref target_mesh; + Ref source_mesh = parent->get_mesh(); + if (!source_mesh.is_valid()) { + print_error("Source mesh is invalid!"); + return {}; + } + + print_line("using parent mesh"); + print_line(vformat("Source mesh type: %s", source_mesh->get_class())); - // 2) Pick which Mesh to read triangles from - Ref target; switch (proxy_mode) { case PROXY_NONE: - target = cast_to(get_parent())->get_mesh(); + target_mesh = source_mesh; break; - case PROXY_CONVEX: { - Ref hull_shape = cast_to(get_parent())->get_mesh()->create_convex_shape(); - ConvexPolygonShape3D *convex = cast_to(hull_shape.ptr()); - if (!convex) { - ERR_PRINT("SteamAudioStaticMesh: convex cast failed"); - return; - } - Ref debug_mesh = convex->get_debug_mesh(); - if (!debug_mesh.is_valid()) { - ERR_PRINT("SteamAudioStaticMesh: debug mesh invalid"); - return; - } - target = debug_mesh; + case PROXY_AUTO: + break; - } + case PROXY_CUSTOM: - if (!custom_proxy_mesh.is_valid()) { - ERR_PRINT("SteamAudioStaticMesh: custom_proxy_mesh not set"); - return; - } - target = custom_proxy_mesh; + break; default: - ERR_PRINT("SteamAudioStaticMesh: unknown proxy_mode"); - return; + print_error("Unknown proxy mode!"); + return {}; } + return target_mesh; - if (!target.is_valid()) { - ERR_PRINT("SteamAudioStaticMesh: target mesh invalid"); +} + + + +void SteamAudioStaticMesh::build_mesh(IPLScene scene) { + if (!scene) { + ERR_PRINT("Invalid IPLScene passed to build_mesh"); return; } - // 3) For each surface, build and add a Steam Audio static mesh - const int surface_count = target->get_surface_count(); - for (int s = 0; s < surface_count; ++s) { - Array arrays = target->surface_get_arrays(s); - PackedVector3Array verts = arrays[Mesh::ARRAY_VERTEX]; - PackedInt32Array idxs = arrays[Mesh::ARRAY_INDEX]; + struct SurfaceData { + PackedVector3Array vertices; + PackedInt32Array indices; + IPLMaterial material; + }; - // --- build IPLMaterial from metadata or default_material --- - Ref gm = target->surface_get_material(s); - Ref sa_mat; - if (gm.is_valid() && gm->has_meta("steam_audio")) { - sa_mat = gm->get_meta("steam_audio"); - } - if (!sa_mat.is_valid()) { - sa_mat = default_material; - } - IPLMaterial material{}; // zero initialize all fields - // absorption[0..2] - material.absorption[0] = sa_mat->get_absorption().x; - material.absorption[1] = sa_mat->get_absorption().y; - material.absorption[2] = sa_mat->get_absorption().z; - // scattering - material.scattering = sa_mat->get_scattering(); - // transmission[0..2] - material.transmission[0] = sa_mat->get_transmission().x; - material.transmission[1] = sa_mat->get_transmission().y; - material.transmission[2] = sa_mat->get_transmission().z; + Vector surface_data; + print_line("building a static mesh"); + Ref mesh = get_proxy_mesh(); - // --- fill out static mesh settings --- - IPLStaticMeshSettings settings{}; + if (mesh.is_null()) { + ERR_PRINT("Failed to get valid proxy mesh"); + return; + } + print_line("got the mesh"); - // Number of vertices - settings.numVertices = verts.size(); + for (int surface_idx = 0; surface_idx < mesh->get_surface_count(); surface_idx++) { + SurfaceData data; + Array arrays = mesh->surface_get_arrays(surface_idx); - // reinterpret Godot’s Vector3 buffer as Steam Audio’s IPLVector3[] - const IPLVector3 *raw_vertices = reinterpret_cast(verts.ptr()); - - // drop the const so it matches IPLStaticMeshSettings::vertices (IPLVector3*) - IPLVector3 *writable_vertices = const_cast(raw_vertices); - settings.vertices = writable_vertices; - - // Number of triangles - settings.numTriangles = idxs.size() / 3; - - // build triangle array - std::vector triangles; - triangles.reserve(settings.numTriangles); - for (int i = 0; i < settings.numTriangles; ++i) { - IPLTriangle tri{}; - tri.indices[0] = static_cast(idxs[3*i + 0]); - tri.indices[1] = static_cast(idxs[3*i + 1]); - tri.indices[2] = static_cast(idxs[3*i + 2]); - triangles.push_back(tri); - } - settings.triangles = triangles.data(); - settings.numMaterials = 1; - settings.materials = &material; - - // --- create & add to Steam Audio scene --- - /*IPLStaticMesh sm = nullptr; - IPLerror err = iplStaticMeshCreate(global_scene, &settings, &sm); - if (err != IPL_STATUS_SUCCESS) { - ERR_PRINT("SteamAudioStaticMesh: iplStaticMeshCreate failed"); + if (arrays.is_empty()) { + ERR_PRINT(vformat("Surface %d has no arrays data", surface_idx)); continue; } - iplStaticMeshAdd(sm, global_scene); - static_meshes.push_back(sm);*/ + + // Get vertices and indices + PackedVector3Array vertices = arrays[Mesh::ARRAY_VERTEX]; + PackedInt32Array indices = arrays[Mesh::ARRAY_INDEX]; + + if (vertices.is_empty()) { + ERR_PRINT(vformat("Surface %d has no vertices", surface_idx)); + continue; + } + + // If indices array is empty, create one from vertices + if (indices.is_empty()) { + indices.resize(vertices.size()); + for (int i = 0; i < vertices.size(); i++) { + indices[i] = i; + } + } + + // Create triangles if indices count is not a multiple of 3 + if (indices.size() % 3 != 0) { + print_line("Surface " + itos(surface_idx) + " is not triangulated, converting to triangles"); + PackedInt32Array new_indices; + + // Simple triangulation for convex polygons + for (int i = 0; i < indices.size() - 2; i++) { + new_indices.push_back(indices[0]); + new_indices.push_back(indices[i + 1]); + new_indices.push_back(indices[i + 2]); + } + + indices = new_indices; + } + + data.vertices = vertices; + data.indices = indices; + + // Get material for the surface + Ref material = mesh->surface_get_material(surface_idx); + if (material.is_valid() && material->has_meta("steam_audio_material")) { + Ref steam_audio_material = material->get_meta("steam_audio_material"); + if (steam_audio_material.is_valid()) { + SteamMaterial steam_material = steam_audio_material->get_steam_material(); + data.material = SteamAudio::to_ipl_material(steam_material); + } else { + WARN_PRINT(vformat("Invalid Steam Audio material for surface %d", surface_idx)); + data.material = SteamAudio::to_ipl_material(SteamAudioMaterial::get_default_material()); + } + } else { + data.material = SteamAudio::to_ipl_material(SteamAudioMaterial::get_default_material()); + } + + surface_data.push_back(data); } - // 4) Commit all additions at once - // iplSceneCommit(global_scene); + if (surface_data.is_empty()) { + ERR_PRINT("No valid surfaces found in mesh"); + return; + } + + // Calculate total counts + int total_vertices = 0; + int total_triangles = 0; + for (const SurfaceData &data: surface_data) { + total_vertices += data.vertices.size(); + total_triangles += data.indices.size() / 3; + } + + if (total_vertices == 0 || total_triangles == 0) { + ERR_PRINT("Mesh has no vertices or triangles"); + return; + } + + // Prepare final data arrays + PackedFloat32Array final_vertices; + PackedInt32Array final_triangles; + Vector final_materials; + PackedInt32Array material_indices; + + final_vertices.resize(total_vertices * 3); + final_triangles.resize(total_triangles * 3); + material_indices.resize(total_triangles); + + // Combine all surfaces + int vertex_offset = 0; + int triangle_offset = 0; + int material_index = 0; + + for (const SurfaceData &data: surface_data) { + // Copy vertices + for (int i = 0; i < data.vertices.size(); i++) { + const Vector3 &v = data.vertices[i]; + final_vertices[vertex_offset * 3 + 0] = v.x; + final_vertices[vertex_offset * 3 + 1] = v.y; + final_vertices[vertex_offset * 3 + 2] = v.z; + vertex_offset++; + } + + // Copy triangles + int base_vertex = vertex_offset - data.vertices.size(); + for (int i = 0; i < data.indices.size(); i += 3) { + final_triangles[triangle_offset * 3 + 0] = base_vertex + data.indices[i + 0]; + final_triangles[triangle_offset * 3 + 1] = base_vertex + data.indices[i + 1]; + final_triangles[triangle_offset * 3 + 2] = base_vertex + data.indices[i + 2]; + + material_indices[triangle_offset] = material_index; + triangle_offset++; + } + + final_materials.push_back(data.material); + material_index++; + } + + // Create Steam Audio static mesh + IPLStaticMeshSettings mesh_settings = {}; + mesh_settings.numVertices = total_vertices; + mesh_settings.numTriangles = total_triangles; + mesh_settings.numMaterials = final_materials.size(); + mesh_settings.vertices = const_cast(reinterpret_cast(final_vertices.ptr())); + mesh_settings.triangles = const_cast(reinterpret_cast(final_triangles.ptr())); + mesh_settings.materials = const_cast(final_materials.ptr()); + mesh_settings.materialIndices = const_cast(material_indices.ptr()); + + IPLStaticMesh static_mesh; + IPLerror error = iplStaticMeshCreate(scene, &mesh_settings, &static_mesh); + if (error != IPL_STATUS_SUCCESS) { + ERR_PRINT(vformat("Failed to create static mesh. Error code: %d", error)); + return; + } + + print_line(vformat("Successfully created static mesh with %d vertices and %d triangles", total_vertices, + total_triangles)); } + + +void SteamAudioStaticMesh::update_static_mesh() { + return; +} + diff --git a/src/steam_audio_static_mesh.h b/src/steam_audio_static_mesh.hpp similarity index 63% rename from src/steam_audio_static_mesh.h rename to src/steam_audio_static_mesh.hpp index e64a6e4..b6bab11 100644 --- a/src/steam_audio_static_mesh.h +++ b/src/steam_audio_static_mesh.hpp @@ -1,24 +1,23 @@ // steam_audio_proxy_mesh.h #pragma once -#include -#include -#include -#include -#include #include +#include #include - -#include "steam_audio_material.h" +#include +#include +#include +#include +#include +#include +#include // IPLStaticMesh, IPLStaticMeshSettings, IPLMaterial +#include "steam_audio.hpp" +#include "steam_audio_material.hpp" using namespace godot; // Proxy generation modes -enum ProxyMode { - PROXY_NONE = 0, - PROXY_CONVEX = 1, - PROXY_CUSTOM = 2, -}; + class SteamAudioStaticMesh : public Node3D { GDCLASS(SteamAudioStaticMesh, Node3D) @@ -26,9 +25,10 @@ class SteamAudioStaticMesh : public Node3D { private: int proxy_mode = PROXY_NONE; Ref custom_proxy_mesh; - Ref default_material; Vector static_meshes; + bool needs_update = false; + protected: static void _bind_methods(); @@ -44,9 +44,14 @@ public: void set_custom_proxy_mesh(Ref mesh); Ref get_custom_proxy_mesh() const; - void set_default_material(Ref mat); - Ref get_default_material() const; + bool get_needs_update() { + return needs_update; + } + void update_static_mesh(); + void build_mesh(IPLScene scene); + static Vector _instances; + static const Vector& get_all_static_meshes(); // Regenerates Steam Audio proxies - void generate_proxy_mesh(); + Ref get_proxy_mesh(); }; diff --git a/support_files/SteamAudioEditorPlugin.gd b/support_files/SteamAudioEditorPlugin.gd new file mode 100644 index 0000000..43fe985 --- /dev/null +++ b/support_files/SteamAudioEditorPlugin.gd @@ -0,0 +1,18 @@ +@tool +extends EditorPlugin + +const AUTOLOAD_NAME = "STEAM_AUDIO" +func _enable_plugin() -> void: + add_autoload_singleton(AUTOLOAD_NAME,"res://addons/SteamAudioGodot/steam_audio_manager.tscn") + +func _enter_tree() -> void: + # Initialization of the plugin goes here. + pass + +func _exit_tree() -> void: + # Clean-up of the plugin goes here. + pass + +func _disable_plugin() -> void: + remove_autoload_singleton(AUTOLOAD_NAME) + diff --git a/support_files/plugin.cfg b/support_files/plugin.cfg new file mode 100644 index 0000000..59fa233 --- /dev/null +++ b/support_files/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Steam Audio Godot" +description="adds Steam Audio to Godot" +author="The River Nyx" +version="" +script="SteamAudioEditorPlugin.gd" diff --git a/support_files/steam_audio_manager.tscn b/support_files/steam_audio_manager.tscn new file mode 100644 index 0000000..d746bd0 --- /dev/null +++ b/support_files/steam_audio_manager.tscn @@ -0,0 +1,3 @@ +[gd_scene load_steps=0 format=3 uid="uid://b28pa3dtdpc8d"] + +[node name="SteamAudio" type="SteamAudio"]