did things

This commit is contained in:
Nyx
2025-08-25 21:45:13 -06:00
parent a79a7551b3
commit 17380c2c60
43 changed files with 1147 additions and 624 deletions

View File

@@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SteamAudioGodot" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="-e" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$PROJECT_DIR$/demo" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SteamAudioGodot" TARGET_NAME="SteamAudioGodot" CONFIG_NAME="Debug" RUN_PATH="$PROJECT_DIR$/../godot-engine/bin/godot.windows.editor.dev.x86_64.console.exe">
<method v="2">
<option name="CMake.Install" enabled="true" />
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -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}

4
demo/.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
demo/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
demo/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

1
demo/icon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

43
demo/icon.svg.import Normal file
View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ch0aljsxfwrd6"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

15
demo/project.godot Normal file
View File

@@ -0,0 +1,15 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="demo"
config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg"

View File

@@ -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}

View File

@@ -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" {

View File

@@ -3,12 +3,13 @@
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/core/class_db.hpp>
#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;

View File

@@ -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<NodePath> 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 Godots 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;
}

View File

@@ -1,81 +0,0 @@
#pragma once
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/thread.hpp>
#include <godot_cpp/classes/time.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/godot.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <phonon.h>
#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<SteamAudioDynamicMesh> dynamic_geometry;
Array<SteamAudioStaticMesh> static_geometry;
Array<SteamAudioSource> sources;
Ref<Thread> reflections_thread;
Ref<Thread> 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;}
};

61
src/steam_audio.hpp Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/wrapped.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/godot.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <phonon.h>
#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;}
};

View File

@@ -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*> SteamAudioDynamicMesh::_instances;
SteamAudioDynamicMesh::SteamAudioDynamicMesh() {
_instances.push_back(this);
}
SteamAudioDynamicMesh::~SteamAudioDynamicMesh() {
_instances.erase(this);
}
const Vector<SteamAudioDynamicMesh *> &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 worldspace 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> mesh = cast_to<MeshInstance3D>(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 staticmesh settings
IPLStaticMeshSettings sms{};
sms.numVertices = verts.size();
sms.vertices = reinterpret_cast<IPLVector3*>(const_cast<Vector3*>(verts.ptr()));
sms.numTriangles = idxs.size() / 3;
// convert PoolIntArray → IPLTriangle*
// …youd 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 its ready to be instanced :contentReference[oaicite:1]{index=1}
iplSceneCommit(proxy_subscene);
void SteamAudioDynamicMesh::set_custom_proxy_mesh(Ref<Mesh> 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<Mesh> 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<Mesh> SteamAudioDynamicMesh::get_proxy_mesh() {
print_line("getting the proxy mesh");
print_line(vformat("proxy_mode: %f", proxy_mode));
MeshInstance3D *parent = cast_to<MeshInstance3D>(get_parent());
if (!parent) {
print_error("Parent is not a MeshInstance3D!");
return {};
}
Ref<Mesh> target_mesh;
Ref<Mesh> 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<SurfaceData> surface_data;
print_line("building a static mesh");
Ref<Mesh> 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> material = mesh->surface_get_material(surface_idx);
if (material.is_valid() && material->has_meta("steam_audio_material")) {
Ref<SteamAudioMaterial> 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<IPLMaterial> 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<IPLVector3 *>(reinterpret_cast<const IPLVector3 *>(final_vertices.ptr()));
mesh_settings.triangles = const_cast<IPLTriangle *>(reinterpret_cast<const IPLTriangle *>(final_triangles.ptr()));
mesh_settings.materials = const_cast<IPLMaterial *>(final_materials.ptr());
mesh_settings.materialIndices = const_cast<IPLint32 *>(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;
}
}

View File

@@ -1,31 +0,0 @@
#pragma once
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <phonon.h>
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 meshs raw geometry
void build_subscene();
public:
SteamAudioDynamicMesh();
~SteamAudioDynamicMesh() override;
void _ready() override;
void _process(double delta) override;
protected:
static void _bind_methods();
};

View File

@@ -0,0 +1,65 @@
#pragma once
#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/audio_server.hpp>
#include <godot_cpp/classes/convex_polygon_shape3d.hpp>
#include <godot_cpp/classes/material.hpp>
#include <godot_cpp/classes/mesh.hpp>
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/classes/shape3d.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/packed_vector3_array.hpp>
#include <phonon.h> // IPLStaticMesh, IPLStaticMeshSettings, IPLMaterial
#include "steam_audio.hpp"
#include "steam_audio_material.hpp"
#include <algorithm>
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<Mesh> 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> mesh);
Ref<Mesh> 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<SteamAudioDynamicMesh*> _instances;
static const Vector<SteamAudioDynamicMesh*>& get_all_dynamic_meshes();
// Regenerates Steam Audio proxies
Ref<Mesh> get_proxy_mesh();
void update_dynamic_mesh();
};

View File

@@ -1,7 +0,0 @@
// steam_audio_globals.h
#pragma once
#include <phonon.h>
#include <godot_cpp/core/class_db.hpp>
extern IPLContext global_context;
extern IPLScene global_scene;
extern IPLSimulator global_simulator;

View File

@@ -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<float>(transform.origin.x),
static_cast<float>(transform.origin.y),
static_cast<float>(transform.origin.z)
};
Vector3 godot_fwd = transform.basis.xform(Vector3(0, 0, -1));
IPLVector3 fwd = {
static_cast<float>(godot_fwd.x),
static_cast<float>(godot_fwd.y),
static_cast<float>(godot_fwd.z)
};
Vector3 godot_up = transform.basis.xform(Vector3(0, 1, 0));
IPLVector3 up = {
static_cast<float>(godot_up.x),
static_cast<float>(godot_up.y),
static_cast<float>(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);
}

View File

@@ -1,24 +0,0 @@
#pragma once
#include<godot_cpp/godot.hpp>
#include <godot_cpp/classes/audio_listener3d.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/classes/wrapped.hpp>
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:
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include <godot_cpp/classes/audio_listener3d.hpp>
#include <godot_cpp/classes/wrapped.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/godot.hpp>
#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);
};

View File

@@ -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;
}

View File

@@ -1,27 +1,32 @@
#pragma once
#include <godot_cpp/variant/vector3.hpp>
#include <godot_cpp/classes/resource.hpp>
#include "godot_cpp/godot.hpp"
#include <godot_cpp/core/class_db.hpp>
#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;
};

View File

@@ -0,0 +1,16 @@
#include "steam_audio_server.hpp"
using namespace godot;
SteamAudioServer::SteamAudioServer() {
}
SteamAudioServer::~SteamAudioServer() {
}
// Core Godot Methods
void SteamAudioServer::_bind_methods() {
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include <godot_cpp/classes/audio_frame.hpp>
#include <godot_cpp/classes/scene_tree.hpp>
#include <godot_cpp/classes/wrapped.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/godot.hpp>
#include <phonon.h>
#include <steam_audio.hpp>
#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
};

View File

@@ -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 *> 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 *> &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;
}
}

View File

@@ -1,29 +0,0 @@
#pragma once
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/audio_stream_generator.hpp>
#include <godot_cpp/classes/audio_stream_generator_playback.hpp>
#include <godot_cpp/classes/audio_stream_player3d.hpp>
#include <godot_cpp/godot.hpp>
#include <godot_cpp/core/class_db.hpp>
#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();
};

View File

@@ -0,0 +1,63 @@
#pragma once
#include <godot_cpp/classes/gd_extension.hpp>
#include <godot_cpp/classes/audio_server.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/godot.hpp>
#include "phonon.h"
#include <godot_cpp/classes/audio_stream_generator.hpp>
#include <godot_cpp/classes/audio_stream_generator_playback.hpp>
#include <godot_cpp/classes/audio_stream_player3d.hpp>
#include <godot_cpp/classes/node3d.hpp>
#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<AudioStreamGeneratorPlayback> playback;
Vector<AudioFrame> 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<SteamAudioSource*> _instances;
static const Vector<SteamAudioSource*>& get_all_sources();
[[nodiscard]] bool get_needs_update() const {
return needs_update;}
};

View File

@@ -1,25 +1,23 @@
// steam_audio_static_mesh.cpp
#include "steam_audio_static_mesh.h"
#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/convex_polygon_shape3d.hpp>
#include <godot_cpp/classes/shape3d.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/packed_vector3_array.hpp>
#include <phonon.h> // IPLStaticMesh, IPLStaticMeshSettings, IPLMaterial
#include "steam_audio_static_mesh.hpp"
using namespace godot;
SteamAudioStaticMesh::SteamAudioStaticMesh() = default;
Vector<SteamAudioStaticMesh*> 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 *> &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> mesh) {
custom_proxy_mesh = mesh;
}
Ref<Mesh> SteamAudioStaticMesh::get_custom_proxy_mesh() const {
return custom_proxy_mesh;
}
void SteamAudioStaticMesh::set_default_material(Ref<SteamAudioMaterial> mat) {
default_material = mat;
}
Ref<SteamAudioMaterial> 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<Mesh> SteamAudioStaticMesh::get_proxy_mesh() {
print_line("getting the proxy mesh");
print_line(vformat("proxy_mode: %f", proxy_mode));
MeshInstance3D* parent = cast_to<MeshInstance3D>(get_parent());
if (!parent) {
print_error("Parent is not a MeshInstance3D!");
return {};
}
Ref<Mesh> target_mesh;
Ref<Mesh> 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<Mesh> target;
switch (proxy_mode) {
case PROXY_NONE:
target = cast_to<MeshInstance3D>(get_parent())->get_mesh();
target_mesh = source_mesh;
break;
case PROXY_CONVEX: {
Ref<Shape3D> hull_shape = cast_to<MeshInstance3D>(get_parent())->get_mesh()->create_convex_shape();
ConvexPolygonShape3D *convex = cast_to<ConvexPolygonShape3D>(hull_shape.ptr());
if (!convex) {
ERR_PRINT("SteamAudioStaticMesh: convex cast failed");
return;
}
Ref<ArrayMesh> 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<Material> gm = target->surface_get_material(s);
Ref<SteamAudioMaterial> 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<SurfaceData> surface_data;
print_line("building a static mesh");
Ref<Mesh> 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 Godots Vector3 buffer as Steam Audios IPLVector3[]
const IPLVector3 *raw_vertices = reinterpret_cast<const IPLVector3 *>(verts.ptr());
// drop the const so it matches IPLStaticMeshSettings::vertices (IPLVector3*)
IPLVector3 *writable_vertices = const_cast<IPLVector3 *>(raw_vertices);
settings.vertices = writable_vertices;
// Number of triangles
settings.numTriangles = idxs.size() / 3;
// build triangle array
std::vector<IPLTriangle> triangles;
triangles.reserve(settings.numTriangles);
for (int i = 0; i < settings.numTriangles; ++i) {
IPLTriangle tri{};
tri.indices[0] = static_cast<uint32_t>(idxs[3*i + 0]);
tri.indices[1] = static_cast<uint32_t>(idxs[3*i + 1]);
tri.indices[2] = static_cast<uint32_t>(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> material = mesh->surface_get_material(surface_idx);
if (material.is_valid() && material->has_meta("steam_audio_material")) {
Ref<SteamAudioMaterial> 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<IPLMaterial> 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<IPLVector3 *>(reinterpret_cast<const IPLVector3 *>(final_vertices.ptr()));
mesh_settings.triangles = const_cast<IPLTriangle *>(reinterpret_cast<const IPLTriangle *>(final_triangles.ptr()));
mesh_settings.materials = const_cast<IPLMaterial *>(final_materials.ptr());
mesh_settings.materialIndices = const_cast<IPLint32 *>(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;
}

View File

@@ -1,24 +1,23 @@
// steam_audio_proxy_mesh.h
#pragma once
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/classes/mesh.hpp>
#include <godot_cpp/classes/shape3d.hpp>
#include <godot_cpp/classes/convex_polygon_shape3d.hpp>
#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/convex_polygon_shape3d.hpp>
#include <godot_cpp/classes/material.hpp>
#include "steam_audio_material.h"
#include <godot_cpp/classes/mesh.hpp>
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/classes/shape3d.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/packed_vector3_array.hpp>
#include <phonon.h> // 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<Mesh> custom_proxy_mesh;
Ref<SteamAudioMaterial> default_material;
Vector<IPLStaticMesh> static_meshes;
bool needs_update = false;
protected:
static void _bind_methods();
@@ -44,9 +44,14 @@ public:
void set_custom_proxy_mesh(Ref<Mesh> mesh);
Ref<Mesh> get_custom_proxy_mesh() const;
void set_default_material(Ref<SteamAudioMaterial> mat);
Ref<SteamAudioMaterial> get_default_material() const;
bool get_needs_update() {
return needs_update;
}
void update_static_mesh();
void build_mesh(IPLScene scene);
static Vector<SteamAudioStaticMesh*> _instances;
static const Vector<SteamAudioStaticMesh*>& get_all_static_meshes();
// Regenerates Steam Audio proxies
void generate_proxy_mesh();
Ref<Mesh> get_proxy_mesh();
};

View File

@@ -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)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

7
support_files/plugin.cfg Normal file
View File

@@ -0,0 +1,7 @@
[plugin]
name="Steam Audio Godot"
description="adds Steam Audio to Godot"
author="The River Nyx"
version=""
script="SteamAudioEditorPlugin.gd"

View File

@@ -0,0 +1,3 @@
[gd_scene load_steps=0 format=3 uid="uid://b28pa3dtdpc8d"]
[node name="SteamAudio" type="SteamAudio"]