summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/project_settings.cpp2
-rw-r--r--doc/classes/PhysicsDirectBodyState.xml9
-rw-r--r--editor/create_dialog.cpp119
-rw-r--r--editor/editor_data.cpp35
-rw-r--r--editor/editor_data.h3
-rw-r--r--editor/import/resource_importer_wav.cpp114
-rw-r--r--editor/import/resource_importer_wav.h113
-rw-r--r--editor/plugins/spatial_editor_plugin.cpp120
-rw-r--r--editor/plugins/spatial_editor_plugin.h7
-rw-r--r--editor/scene_tree_dock.cpp53
-rw-r--r--modules/bullet/rigid_body_bullet.cpp7
-rw-r--r--modules/bullet/rigid_body_bullet.h4
-rw-r--r--modules/bullet/space_bullet.cpp5
-rw-r--r--modules/gdscript/gdscript_compiler.cpp8
-rw-r--r--modules/gdscript/gdscript_function.cpp34
-rw-r--r--modules/gdscript/gdscript_functions.cpp2
-rw-r--r--modules/gdscript/gdscript_parser.cpp116
-rw-r--r--modules/visual_script/visual_script_editor.cpp4
-rw-r--r--scene/resources/audio_stream_sample.cpp74
-rw-r--r--scene/resources/audio_stream_sample.h2
-rw-r--r--servers/audio/audio_effect.h1
-rw-r--r--servers/audio/effects/audio_effect_record.cpp259
-rw-r--r--servers/audio/effects/audio_effect_record.h103
-rw-r--r--servers/audio_server.cpp4
-rw-r--r--servers/physics/body_sw.h3
-rw-r--r--servers/physics/collision_solver_sat.cpp4
-rw-r--r--servers/physics_server.cpp1
-rw-r--r--servers/physics_server.h1
-rw-r--r--servers/register_server_types.cpp2
29 files changed, 947 insertions, 262 deletions
diff --git a/core/project_settings.cpp b/core/project_settings.cpp
index c24b7d5a87..146b4870e8 100644
--- a/core/project_settings.cpp
+++ b/core/project_settings.cpp
@@ -959,7 +959,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("application/config/name", "");
GLOBAL_DEF("application/run/main_scene", "");
- custom_prop_info["application/run/main_scene"] = PropertyInfo(Variant::STRING, "application/run/main_scene", PROPERTY_HINT_FILE, "tscn,scn,res");
+ custom_prop_info["application/run/main_scene"] = PropertyInfo(Variant::STRING, "application/run/main_scene", PROPERTY_HINT_FILE, "*.tscn,*.scn,*.res");
GLOBAL_DEF("application/run/disable_stdout", false);
GLOBAL_DEF("application/run/disable_stderr", false);
GLOBAL_DEF("application/config/use_custom_user_dir", false);
diff --git a/doc/classes/PhysicsDirectBodyState.xml b/doc/classes/PhysicsDirectBodyState.xml
index 64b2900f73..5117c9ef6c 100644
--- a/doc/classes/PhysicsDirectBodyState.xml
+++ b/doc/classes/PhysicsDirectBodyState.xml
@@ -115,6 +115,15 @@
<description>
</description>
</method>
+ <method name="get_contact_impulse" qualifiers="const">
+ <return type="float">
+ </return>
+ <argument index="0" name="contact_idx" type="int">
+ </argument>
+ <description>
+ Impulse created by the contact. Only implemented for Bullet physics.
+ </description>
+ </method>
<method name="get_contact_local_normal" qualifiers="const">
<return type="Vector3">
</return>
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 6b2a072e20..3e0c1f2d53 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -40,6 +40,11 @@
void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode) {
+ type_list.clear();
+ ClassDB::get_class_list(&type_list);
+ ScriptServer::get_global_class_list(&type_list);
+ type_list.sort_custom<StringName::AlphCompare>();
+
recent->clear();
FileAccess *f = FileAccess::open(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("create_recent." + base_type), FileAccess::READ);
@@ -173,10 +178,28 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p
if (p_types.has(p_type))
return;
- if (!ClassDB::is_parent_class(p_type, base_type) || p_type == base_type)
+
+ bool cpp_type = ClassDB::class_exists(p_type);
+ EditorData &ed = EditorNode::get_singleton()->get_editor_data();
+
+ if (p_type == base_type)
return;
- String inherits = ClassDB::get_parent_class(p_type);
+ if (cpp_type) {
+ if (!ClassDB::is_parent_class(p_type, base_type))
+ return;
+ } else {
+ if (!ScriptServer::is_global_class(p_type) || !ed.script_class_is_parent(p_type, base_type))
+ return;
+
+ String script_path = ScriptServer::get_global_class_path(p_type);
+ if (script_path.find("res://addons/", 0) != -1) {
+ if (!EditorNode::get_singleton()->is_addon_plugin_enabled(script_path.get_slicec('/', 3)))
+ return;
+ }
+ }
+
+ String inherits = cpp_type ? ClassDB::get_parent_class(p_type) : ed.script_class_get_base(p_type);
TreeItem *parent = p_root;
@@ -189,17 +212,32 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p
if (p_types.has(inherits))
parent = p_types[inherits];
+ else if (ScriptServer::is_global_class(inherits))
+ return;
}
+ bool can_instance = (cpp_type && ClassDB::can_instance(p_type)) || ScriptServer::is_global_class(p_type);
+
TreeItem *item = search_options->create_item(parent);
- item->set_text(0, p_type);
- if (!ClassDB::can_instance(p_type)) {
+ if (cpp_type) {
+ item->set_text(0, p_type);
+ } else {
+ item->set_metadata(0, p_type);
+ item->set_text(0, p_type + " (" + ScriptServer::get_global_class_path(p_type).get_file() + ")");
+ }
+ if (!can_instance) {
item->set_custom_color(0, get_color("disabled_font_color", "Editor"));
item->set_selectable(0, false);
} else {
bool is_search_subsequence = search_box->get_text().is_subsequence_ofi(p_type);
String to_select_type = *to_select ? (*to_select)->get_text(0) : "";
- bool current_item_is_preffered = ClassDB::is_parent_class(p_type, preferred_search_result_type) && !ClassDB::is_parent_class(to_select_type, preferred_search_result_type);
+ to_select_type = to_select_type.split(" ")[0];
+ bool current_item_is_preffered;
+ if (cpp_type) {
+ current_item_is_preffered = ClassDB::is_parent_class(p_type, preferred_search_result_type) && !ClassDB::is_parent_class(to_select_type, preferred_search_result_type);
+ } else {
+ current_item_is_preffered = ed.script_class_is_parent(p_type, preferred_search_result_type) && !ed.script_class_is_parent(to_select_type, preferred_search_result_type);
+ }
if (*to_select && p_type.length() < (*to_select)->get_text(0).length()) {
current_item_is_preffered = true;
}
@@ -217,16 +255,19 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p
// don't collapse the root node
collapse &= (item != p_root);
// don't collapse abstract nodes on the first tree level
- collapse &= ((parent != p_root) || (ClassDB::can_instance(p_type)));
+ collapse &= ((parent != p_root) || (can_instance));
item->set_collapsed(collapse);
}
const String &description = EditorHelp::get_doc_data()->class_list[p_type].brief_description;
item->set_tooltip(0, description);
- if (has_icon(p_type, "EditorIcons")) {
+ if (cpp_type && has_icon(p_type, "EditorIcons")) {
item->set_icon(0, get_icon(p_type, "EditorIcons"));
+ } else if (!cpp_type && has_icon(ScriptServer::get_global_class_base(p_type), "EditorIcons")) {
+
+ item->set_icon(0, get_icon(ScriptServer::get_global_class_base(p_type), "EditorIcons"));
}
p_types[p_type] = item;
@@ -243,47 +284,38 @@ void CreateDialog::_update_search() {
_parse_fs(EditorFileSystem::get_singleton()->get_filesystem());
*/
- List<StringName> global_classes;
- ScriptServer::get_global_class_list(&global_classes);
-
- Map<String, List<String> > global_class_map;
- for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) {
- String base = ScriptServer::get_global_class_base(E->get());
- if (!global_class_map.has(base)) {
- global_class_map[base] = List<String>();
- }
- global_class_map[base].push_back(E->get());
- }
-
HashMap<String, TreeItem *> types;
TreeItem *root = search_options->create_item();
+ EditorData &ed = EditorNode::get_singleton()->get_editor_data();
root->set_text(0, base_type);
if (has_icon(base_type, "EditorIcons")) {
root->set_icon(0, get_icon(base_type, "EditorIcons"));
}
- List<StringName>::Element *I = type_list.front();
TreeItem *to_select = search_box->get_text() == base_type ? root : NULL;
- for (; I; I = I->next()) {
+ for (List<StringName>::Element *I = type_list.front(); I; I = I->next()) {
String type = I->get();
+ bool cpp_type = ClassDB::class_exists(type);
if (base_type == "Node" && type.begins_with("Editor"))
continue; // do not show editor nodes
- if (!ClassDB::can_instance(type))
+ if (cpp_type && !ClassDB::can_instance(type))
continue; // can't create what can't be instanced
bool skip = false;
- for (Set<StringName>::Element *E = type_blacklist.front(); E && !skip; E = E->next()) {
- if (ClassDB::is_parent_class(type, E->get()))
- skip = true;
+ if (cpp_type) {
+ for (Set<StringName>::Element *E = type_blacklist.front(); E && !skip; E = E->next()) {
+ if (ClassDB::is_parent_class(type, E->get()))
+ skip = true;
+ }
+ if (skip)
+ continue;
}
- if (skip)
- continue;
if (search_box->get_text() == "") {
add_type(type, types, root, &to_select);
@@ -291,7 +323,7 @@ void CreateDialog::_update_search() {
bool found = false;
String type = I->get();
- while (type != "" && ClassDB::is_parent_class(type, base_type) && type != base_type) {
+ while (type != "" && (cpp_type ? ClassDB::is_parent_class(type, base_type) : ed.script_class_is_parent(type, base_type)) && type != base_type) {
if (search_box->get_text().is_subsequence_ofi(type)) {
found = true;
@@ -305,32 +337,6 @@ void CreateDialog::_update_search() {
add_type(I->get(), types, root, &to_select);
}
- if (global_class_map.has(type) && ClassDB::is_parent_class(type, base_type)) {
- for (List<String>::Element *J = global_class_map[type].front(); J; J = J->next()) {
- bool show = search_box->get_text().is_subsequence_ofi(J->get());
-
- if (!show)
- continue;
-
- if (!types.has(type))
- add_type(type, types, root, &to_select);
-
- TreeItem *ti;
- if (types.has(type))
- ti = types[type];
- else
- ti = search_options->get_root();
-
- TreeItem *item = search_options->create_item(ti);
- item->set_metadata(0, J->get());
- item->set_text(0, J->get() + " (" + ScriptServer::get_global_class_path(J->get()).get_file() + ")");
- item->set_icon(0, _get_editor_icon(type));
- if (!to_select || J->get() == search_box->get_text()) {
- to_select = item;
- }
- }
- }
-
if (EditorNode::get_editor_data().get_custom_types().has(type) && ClassDB::is_parent_class(type, base_type)) {
//there are custom types based on this... cool.
@@ -694,9 +700,6 @@ CreateDialog::CreateDialog() {
is_replace_mode = false;
- ClassDB::get_class_list(&type_list);
- type_list.sort_custom<StringName::AlphCompare>();
-
set_resizable(true);
HSplitContainer *hsc = memnew(HSplitContainer);
@@ -762,4 +765,6 @@ CreateDialog::CreateDialog() {
type_blacklist.insert("PluginScript"); // PluginScript must be initialized before use, which is not possible here
type_blacklist.insert("ScriptCreateDialog"); // This is an exposed editor Node that doesn't have an Editor prefix.
+
+ EDITOR_DEF("interface/editors/derive_script_globals_by_name", true);
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 729f6f50ef..f4ef11eb36 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -853,6 +853,41 @@ void EditorData::get_plugin_window_layout(Ref<ConfigFile> p_layout) {
}
}
+bool EditorData::script_class_is_parent(const String &p_class, const String &p_inherits) {
+ if (!ScriptServer::is_global_class(p_class))
+ return false;
+ String base = script_class_get_base(p_class);
+ while (p_inherits != base) {
+ if (ClassDB::class_exists(base)) {
+ return ClassDB::is_parent_class(base, p_inherits);
+ } else if (ScriptServer::is_global_class(base)) {
+ base = script_class_get_base(base);
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+StringName EditorData::script_class_get_base(const String &p_class) {
+
+ if (!ScriptServer::is_global_class(p_class))
+ return StringName();
+
+ String path = ScriptServer::get_global_class_path(p_class);
+
+ Ref<Script> script = ResourceLoader::load(path, "Script");
+ if (script.is_null())
+ return StringName();
+
+ Ref<Script> base_script = script->get_base_script();
+ if (base_script.is_null()) {
+ return ScriptServer::get_global_class_base(p_class);
+ }
+
+ return script->get_language()->get_global_class_name(base_script->get_path());
+}
+
EditorData::EditorData() {
current_edited_scene = -1;
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 0ecef8ae31..fac6635cd2 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -211,6 +211,9 @@ public:
void notify_edited_scene_changed();
void notify_resource_saved(const Ref<Resource> &p_resource);
+ bool script_class_is_parent(const String &p_class, const String &p_inherits);
+ StringName script_class_get_base(const String &p_class);
+
EditorData();
};
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index fa641871a9..41f5a892eb 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -526,119 +526,5 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
return OK;
}
-void ResourceImporterWAV::_compress_ima_adpcm(const Vector<float> &p_data, PoolVector<uint8_t> &dst_data) {
-
- /*p_sample_data->data = (void*)malloc(len);
- xm_s8 *dataptr=(xm_s8*)p_sample_data->data;*/
-
- static const int16_t _ima_adpcm_step_table[89] = {
- 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
- 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
- 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
- 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
- 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
- 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
- 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
- 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
- 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
- };
-
- static const int8_t _ima_adpcm_index_table[16] = {
- -1, -1, -1, -1, 2, 4, 6, 8,
- -1, -1, -1, -1, 2, 4, 6, 8
- };
-
- int datalen = p_data.size();
- int datamax = datalen;
- if (datalen & 1)
- datalen++;
-
- dst_data.resize(datalen / 2 + 4);
- PoolVector<uint8_t>::Write w = dst_data.write();
-
- int i, step_idx = 0, prev = 0;
- uint8_t *out = w.ptr();
- //int16_t xm_prev=0;
- const float *in = p_data.ptr();
-
- /* initial value is zero */
- *(out++) = 0;
- *(out++) = 0;
- /* Table index initial value */
- *(out++) = 0;
- /* unused */
- *(out++) = 0;
-
- for (i = 0; i < datalen; i++) {
- int step, diff, vpdiff, mask;
- uint8_t nibble;
- int16_t xm_sample;
-
- if (i >= datamax)
- xm_sample = 0;
- else {
-
- xm_sample = CLAMP(in[i] * 32767.0, -32768, 32767);
- /*
- if (xm_sample==32767 || xm_sample==-32768)
- printf("clippy!\n",xm_sample);
- */
- }
-
- //xm_sample=xm_sample+xm_prev;
- //xm_prev=xm_sample;
-
- diff = (int)xm_sample - prev;
-
- nibble = 0;
- step = _ima_adpcm_step_table[step_idx];
- vpdiff = step >> 3;
- if (diff < 0) {
- nibble = 8;
- diff = -diff;
- }
- mask = 4;
- while (mask) {
-
- if (diff >= step) {
-
- nibble |= mask;
- diff -= step;
- vpdiff += step;
- }
-
- step >>= 1;
- mask >>= 1;
- };
-
- if (nibble & 8)
- prev -= vpdiff;
- else
- prev += vpdiff;
-
- if (prev > 32767) {
- //printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip up %i\n",i,xm_sample,prev,diff,vpdiff,prev);
- prev = 32767;
- } else if (prev < -32768) {
- //printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip down %i\n",i,xm_sample,prev,diff,vpdiff,prev);
- prev = -32768;
- }
-
- step_idx += _ima_adpcm_index_table[nibble];
- if (step_idx < 0)
- step_idx = 0;
- else if (step_idx > 88)
- step_idx = 88;
-
- if (i & 1) {
- *out |= nibble << 4;
- out++;
- } else {
- *out = nibble;
- }
- /*dataptr[i]=prev>>8;*/
- }
-}
-
ResourceImporterWAV::ResourceImporterWAV() {
}
diff --git a/editor/import/resource_importer_wav.h b/editor/import/resource_importer_wav.h
index cfce5a31ee..f78ab09e9b 100644
--- a/editor/import/resource_importer_wav.h
+++ b/editor/import/resource_importer_wav.h
@@ -48,7 +48,118 @@ public:
virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const;
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const;
- void _compress_ima_adpcm(const Vector<float> &p_data, PoolVector<uint8_t> &dst_data);
+ static void _compress_ima_adpcm(const Vector<float> &p_data, PoolVector<uint8_t> &dst_data) {
+ /*p_sample_data->data = (void*)malloc(len);
+ xm_s8 *dataptr=(xm_s8*)p_sample_data->data;*/
+
+ static const int16_t _ima_adpcm_step_table[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
+ 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+ 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
+ 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+ 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+ 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+ 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+ };
+
+ static const int8_t _ima_adpcm_index_table[16] = {
+ -1, -1, -1, -1, 2, 4, 6, 8,
+ -1, -1, -1, -1, 2, 4, 6, 8
+ };
+
+ int datalen = p_data.size();
+ int datamax = datalen;
+ if (datalen & 1)
+ datalen++;
+
+ dst_data.resize(datalen / 2 + 4);
+ PoolVector<uint8_t>::Write w = dst_data.write();
+
+ int i, step_idx = 0, prev = 0;
+ uint8_t *out = w.ptr();
+ //int16_t xm_prev=0;
+ const float *in = p_data.ptr();
+
+ /* initial value is zero */
+ *(out++) = 0;
+ *(out++) = 0;
+ /* Table index initial value */
+ *(out++) = 0;
+ /* unused */
+ *(out++) = 0;
+
+ for (i = 0; i < datalen; i++) {
+ int step, diff, vpdiff, mask;
+ uint8_t nibble;
+ int16_t xm_sample;
+
+ if (i >= datamax)
+ xm_sample = 0;
+ else {
+
+ xm_sample = CLAMP(in[i] * 32767.0, -32768, 32767);
+ /*
+ if (xm_sample==32767 || xm_sample==-32768)
+ printf("clippy!\n",xm_sample);
+ */
+ }
+
+ //xm_sample=xm_sample+xm_prev;
+ //xm_prev=xm_sample;
+
+ diff = (int)xm_sample - prev;
+
+ nibble = 0;
+ step = _ima_adpcm_step_table[step_idx];
+ vpdiff = step >> 3;
+ if (diff < 0) {
+ nibble = 8;
+ diff = -diff;
+ }
+ mask = 4;
+ while (mask) {
+
+ if (diff >= step) {
+
+ nibble |= mask;
+ diff -= step;
+ vpdiff += step;
+ }
+
+ step >>= 1;
+ mask >>= 1;
+ };
+
+ if (nibble & 8)
+ prev -= vpdiff;
+ else
+ prev += vpdiff;
+
+ if (prev > 32767) {
+ //printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip up %i\n",i,xm_sample,prev,diff,vpdiff,prev);
+ prev = 32767;
+ } else if (prev < -32768) {
+ //printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip down %i\n",i,xm_sample,prev,diff,vpdiff,prev);
+ prev = -32768;
+ }
+
+ step_idx += _ima_adpcm_index_table[nibble];
+ if (step_idx < 0)
+ step_idx = 0;
+ else if (step_idx > 88)
+ step_idx = 88;
+
+ if (i & 1) {
+ *out |= nibble << 4;
+ out++;
+ } else {
+ *out = nibble;
+ }
+ /*dataptr[i]=prev>>8;*/
+ }
+ }
virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = NULL);
diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp
index 887990ce2c..eab1588a55 100644
--- a/editor/plugins/spatial_editor_plugin.cpp
+++ b/editor/plugins/spatial_editor_plugin.cpp
@@ -4347,6 +4347,9 @@ void SpatialEditor::_menu_item_pressed(int p_option) {
settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50));
} break;
+ case MENU_SNAP_TO_FLOOR: {
+ snap_selected_nodes_to_floor();
+ } break;
case MENU_LOCK_SELECTED: {
List<Node *> &selection = editor_selection->get_selected_node_list();
@@ -4822,6 +4825,119 @@ void SpatialEditor::_refresh_menu_icons() {
tool_button[TOOL_UNLOCK_SELECTED]->set_visible(all_locked);
}
+template <typename T>
+Set<T *> _get_child_nodes(Node *parent_node) {
+ Set<T *> nodes = Set<T *>();
+ T *node = Node::cast_to<T>(parent_node);
+ if (node) {
+ nodes.insert(node);
+ }
+
+ for (int i = 0; i < parent_node->get_child_count(); i++) {
+ Node *child_node = parent_node->get_child(i);
+ Set<T *> child_nodes = _get_child_nodes<T>(child_node);
+ for (typename Set<T *>::Element *I = child_nodes.front(); I; I = I->next()) {
+ nodes.insert(I->get());
+ }
+ }
+
+ return nodes;
+}
+
+Set<RID> _get_physics_bodies_rid(Node *node) {
+ Set<RID> rids = Set<RID>();
+ PhysicsBody *pb = Node::cast_to<PhysicsBody>(node);
+ if (pb) {
+ rids.insert(pb->get_rid());
+ }
+ Set<PhysicsBody *> child_nodes = _get_child_nodes<PhysicsBody>(node);
+ for (Set<PhysicsBody *>::Element *I = child_nodes.front(); I; I = I->next()) {
+ rids.insert(I->get()->get_rid());
+ }
+
+ return rids;
+}
+
+void SpatialEditor::snap_selected_nodes_to_floor() {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+ Dictionary snap_data;
+
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ Spatial *sp = Object::cast_to<Spatial>(E->get());
+ if (sp) {
+ Vector3 from = Vector3();
+ Vector3 position_offset = Vector3();
+
+ // Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin
+ Set<VisualInstance *> vi = _get_child_nodes<VisualInstance>(sp);
+ Set<CollisionShape *> cs = _get_child_nodes<CollisionShape>(sp);
+
+ if (cs.size()) {
+ AABB aabb = sp->get_global_transform().xform(cs.front()->get()->get_shape()->get_debug_mesh()->get_aabb());
+ for (Set<CollisionShape *>::Element *I = cs.front(); I; I = I->next()) {
+ aabb.merge_with(sp->get_global_transform().xform(I->get()->get_shape()->get_debug_mesh()->get_aabb()));
+ }
+ Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);
+ from = aabb.position + size;
+ position_offset.y = from.y - sp->get_global_transform().origin.y;
+ } else if (vi.size()) {
+ AABB aabb = vi.front()->get()->get_transformed_aabb();
+ for (Set<VisualInstance *>::Element *I = vi.front(); I; I = I->next()) {
+ aabb.merge_with(I->get()->get_transformed_aabb());
+ }
+ Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);
+ from = aabb.position + size;
+ position_offset.y = from.y - sp->get_global_transform().origin.y;
+ } else {
+ from = sp->get_global_transform().origin;
+ }
+
+ // We add a bit of margin to the from position to avoid it from snapping
+ // when the spatial is already on a floor and there's another floor under
+ // it
+ from = from + Vector3(0.0, 0.1, 0.0);
+
+ Dictionary d;
+
+ d["from"] = from;
+ d["position_offset"] = position_offset;
+ snap_data[sp] = d;
+ }
+ }
+
+ PhysicsDirectSpaceState *ss = get_tree()->get_root()->get_world()->get_direct_space_state();
+ PhysicsDirectSpaceState::RayResult result;
+
+ Array keys = snap_data.keys();
+
+ if (keys.size()) {
+ undo_redo->create_action("Snap Nodes To Floor");
+
+ for (int i = 0; i < keys.size(); i++) {
+ Node *node = keys[i];
+ Spatial *sp = Object::cast_to<Spatial>(node);
+
+ Dictionary d = snap_data[node];
+ Vector3 from = d["from"];
+ Vector3 position_offset = d["position_offset"];
+
+ Vector3 to = from - Vector3(0.0, 10.0, 0.0);
+ Set<RID> excluded = _get_physics_bodies_rid(sp);
+
+ if (ss->intersect_ray(from, to, result, excluded)) {
+ Transform new_transform = sp->get_global_transform();
+ new_transform.origin.y = result.position.y;
+ new_transform.origin = new_transform.origin - position_offset;
+
+ undo_redo->add_do_method(sp, "set_global_transform", new_transform);
+ undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_transform());
+ }
+ }
+
+ undo_redo->commit_action();
+ }
+}
+
void SpatialEditor::_unhandled_key_input(Ref<InputEvent> p_event) {
if (!is_visible_in_tree() || get_viewport()->gui_has_modal_stack())
@@ -4850,6 +4966,8 @@ void SpatialEditor::_unhandled_key_input(Ref<InputEvent> p_event) {
else if (ED_IS_SHORTCUT("spatial_editor/tool_scale", p_event))
_menu_item_pressed(MENU_TOOL_SCALE);
+ else if (ED_IS_SHORTCUT("spatial_editor/snap_to_floor", p_event))
+ snap_selected_nodes_to_floor();
else if (ED_IS_SHORTCUT("spatial_editor/local_coords", p_event))
if (are_local_coords_enabled()) {
@@ -5209,6 +5327,7 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) {
ED_SHORTCUT("spatial_editor/tool_move", TTR("Tool Move"), KEY_W);
ED_SHORTCUT("spatial_editor/tool_rotate", TTR("Tool Rotate"), KEY_E);
ED_SHORTCUT("spatial_editor/tool_scale", TTR("Tool Scale"), KEY_R);
+ ED_SHORTCUT("spatial_editor/snap_to_floor", TTR("Snap To Floor"), KEY_PAGEDOWN);
ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KEY_MASK_SHIFT + KEY_F);
@@ -5219,6 +5338,7 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) {
hbc_menu->add_child(transform_menu);
p = transform_menu->get_popup();
+ p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTR("Snap object to floor")), MENU_SNAP_TO_FLOOR);
p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTR("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTR("Transform Dialog...")), MENU_TRANSFORM_DIALOG);
diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h
index bf3c0fd831..bd449a28df 100644
--- a/editor/plugins/spatial_editor_plugin.h
+++ b/editor/plugins/spatial_editor_plugin.h
@@ -110,7 +110,6 @@ private:
int index;
String name;
void _menu_option(int p_option);
-
Spatial *preview_node;
AABB *preview_bounds;
Vector<String> selected_files;
@@ -410,7 +409,6 @@ public:
TOOL_LOCK_SELECTED,
TOOL_UNLOCK_SELECTED,
TOOL_MAX
-
};
enum ToolOptions {
@@ -494,7 +492,8 @@ private:
MENU_VIEW_CAMERA_SETTINGS,
MENU_LOCK_SELECTED,
MENU_UNLOCK_SELECTED,
- MENU_VISIBILITY_SKELETON
+ MENU_VISIBILITY_SKELETON,
+ MENU_SNAP_TO_FLOOR
};
Button *tool_button[TOOL_MAX];
@@ -603,7 +602,7 @@ public:
void update_transform_gizmo();
void update_all_gizmos();
-
+ void snap_selected_nodes_to_floor();
void select_gizmo_highlight_axis(int p_axis);
void set_custom_camera(Node *p_camera) { custom_camera = p_camera; }
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 8d38bf39b5..d8982c751c 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -349,21 +349,33 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
break;
Ref<Script> existing = selected->get_script();
- if (existing.is_valid())
- editor->push_item(existing.ptr());
- else {
- String path = selected->get_filename();
- if (path == "") {
- String root_path = editor_data->get_edited_scene_root()->get_filename();
- if (root_path == "") {
- path = "res://" + selected->get_name();
- } else {
- path = root_path.get_base_dir() + "/" + selected->get_name();
+
+ String path = selected->get_filename();
+ if (path == "") {
+ String root_path = editor_data->get_edited_scene_root()->get_filename();
+ if (root_path == "") {
+ path = "res://" + selected->get_name();
+ } else {
+ path = root_path.get_base_dir() + "/" + selected->get_name();
+ }
+ }
+
+ String inherits = selected->get_class();
+ if (existing.is_valid()) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptLanguage *l = ScriptServer::get_language(i);
+ if (l->get_type() == existing->get_class()) {
+ if (EDITOR_GET("interface/editors/derive_script_globals_by_name").operator bool()) {
+ String name = l->get_global_class_name(existing->get_path(), NULL);
+ inherits = editor->get_editor_data().script_class_get_base(name);
+ } else if (l->can_inherit_from_file()) {
+ inherits = "\"" + existing->get_path() + "\"";
+ }
}
}
- script_create_dialog->config(selected->get_class(), path);
- script_create_dialog->popup_centered();
}
+ script_create_dialog->config(inherits, path);
+ script_create_dialog->popup_centered();
} break;
case TOOL_CLEAR_SCRIPT: {
@@ -1426,13 +1438,9 @@ void SceneTreeDock::_script_created(Ref<Script> p_script) {
editor_data->get_undo_redo().add_undo_method(E->get(), "set_script", existing);
}
- editor_data->get_undo_redo().add_do_method(editor, "push_item", p_script.operator->());
- editor_data->get_undo_redo().add_undo_method(editor, "push_item", (Script *)NULL);
-
- editor_data->get_undo_redo().add_do_method(this, "_update_script_button");
- editor_data->get_undo_redo().add_undo_method(this, "_update_script_button");
-
editor_data->get_undo_redo().commit_action();
+
+ editor->push_item(p_script.operator->());
}
void SceneTreeDock::_delete_confirm() {
@@ -1521,16 +1529,9 @@ void SceneTreeDock::_delete_confirm() {
void SceneTreeDock::_update_script_button() {
if (EditorNode::get_singleton()->get_editor_selection()->get_selection().size() == 1) {
- if (EditorNode::get_singleton()->get_editor_selection()->get_selection().front()->key()->get_script().is_null()) {
- button_create_script->show();
- button_clear_script->hide();
- } else {
- button_create_script->hide();
- button_clear_script->show();
- }
+ button_create_script->show();
} else {
button_create_script->hide();
- button_clear_script->hide();
}
}
diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp
index 52453eae17..81a62edba6 100644
--- a/modules/bullet/rigid_body_bullet.cpp
+++ b/modules/bullet/rigid_body_bullet.cpp
@@ -158,6 +158,10 @@ Vector3 BulletPhysicsDirectBodyState::get_contact_local_normal(int p_contact_idx
return body->collisions[p_contact_idx].hitNormal;
}
+float BulletPhysicsDirectBodyState::get_contact_impulse(int p_contact_idx) const {
+ return body->collisions[p_contact_idx].appliedImpulse;
+}
+
int BulletPhysicsDirectBodyState::get_contact_local_shape(int p_contact_idx) const {
return body->collisions[p_contact_idx].local_shape;
}
@@ -407,7 +411,7 @@ void RigidBodyBullet::on_collision_checker_start() {
collisionsCount = 0;
}
-bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, int p_other_shape_index, int p_local_shape_index) {
+bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, const float &p_appliedImpulse, int p_other_shape_index, int p_local_shape_index) {
if (collisionsCount >= maxCollisionsDetection) {
return false;
@@ -418,6 +422,7 @@ bool RigidBodyBullet::add_collision_object(RigidBodyBullet *p_otherObject, const
cd.otherObject = p_otherObject;
cd.hitWorldLocation = p_hitWorldLocation;
cd.hitNormal = p_hitNormal;
+ cd.appliedImpulse = p_appliedImpulse;
cd.other_object_shape = p_other_shape_index;
cd.local_shape = p_local_shape_index;
diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h
index 7dbb5cf870..35af3b90d8 100644
--- a/modules/bullet/rigid_body_bullet.h
+++ b/modules/bullet/rigid_body_bullet.h
@@ -124,6 +124,7 @@ public:
virtual Vector3 get_contact_local_position(int p_contact_idx) const;
virtual Vector3 get_contact_local_normal(int p_contact_idx) const;
+ virtual float get_contact_impulse(int p_contact_idx) const;
virtual int get_contact_local_shape(int p_contact_idx) const;
virtual RID get_contact_collider(int p_contact_idx) const;
@@ -150,6 +151,7 @@ public:
Vector3 hitLocalLocation;
Vector3 hitWorldLocation;
Vector3 hitNormal;
+ float appliedImpulse;
};
struct ForceIntegrationCallback {
@@ -252,7 +254,7 @@ public:
}
bool can_add_collision() { return collisionsCount < maxCollisionsDetection; }
- bool add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, int p_other_shape_index, int p_local_shape_index);
+ bool add_collision_object(RigidBodyBullet *p_otherObject, const Vector3 &p_hitWorldLocation, const Vector3 &p_hitLocalLocation, const Vector3 &p_hitNormal, const float &p_appliedImpulse, int p_other_shape_index, int p_local_shape_index);
void assert_no_constraints();
diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp
index 1ef631c916..8454bea4eb 100644
--- a/modules/bullet/space_bullet.cpp
+++ b/modules/bullet/space_bullet.cpp
@@ -795,19 +795,20 @@ void SpaceBullet::check_body_collision() {
Vector3 collisionWorldPosition;
Vector3 collisionLocalPosition;
Vector3 normalOnB;
+ float appliedImpulse = pt.m_appliedImpulse;
B_TO_G(pt.m_normalWorldOnB, normalOnB);
if (bodyA->can_add_collision()) {
B_TO_G(pt.getPositionWorldOnB(), collisionWorldPosition);
/// pt.m_localPointB Doesn't report the exact point in local space
B_TO_G(pt.getPositionWorldOnB() - contactManifold->getBody1()->getWorldTransform().getOrigin(), collisionLocalPosition);
- bodyA->add_collision_object(bodyB, collisionWorldPosition, collisionLocalPosition, normalOnB, pt.m_index1, pt.m_index0);
+ bodyA->add_collision_object(bodyB, collisionWorldPosition, collisionLocalPosition, normalOnB, appliedImpulse, pt.m_index1, pt.m_index0);
}
if (bodyB->can_add_collision()) {
B_TO_G(pt.getPositionWorldOnA(), collisionWorldPosition);
/// pt.m_localPointA Doesn't report the exact point in local space
B_TO_G(pt.getPositionWorldOnA() - contactManifold->getBody0()->getWorldTransform().getOrigin(), collisionLocalPosition);
- bodyB->add_collision_object(bodyA, collisionWorldPosition, collisionLocalPosition, normalOnB * -1, pt.m_index0, pt.m_index1);
+ bodyB->add_collision_object(bodyA, collisionWorldPosition, collisionLocalPosition, normalOnB * -1, appliedImpulse * -1, pt.m_index0, pt.m_index1);
}
#ifdef DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 93585a5342..fe393957db 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -140,7 +140,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
} break;
case GDScriptParser::DataType::CLASS: {
result.kind = GDScriptDataType::GDSCRIPT;
- if (p_datatype.class_type->name == StringName()) {
+ if (!p_datatype.class_type->owner) {
result.script_type = Ref<GDScript>(main_script);
} else {
result.script_type = class_map[p_datatype.class_type->name];
@@ -482,7 +482,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
Variant script;
int idx = -1;
- if (cn->cast_type.class_type->name == StringName()) {
+ if (!cn->cast_type.class_type->owner) {
script = codegen.script;
} else {
StringName name = cn->cast_type.class_type->name;
@@ -1181,7 +1181,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
Variant script;
int idx = -1;
- if (assign_type.class_type->name == StringName()) {
+ if (!assign_type.class_type->owner) {
script = codegen.script;
} else {
StringName name = assign_type.class_type->name;
@@ -1994,7 +1994,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, GDScript *p_owner
p_script->_signals[name] = p_class->_signals[i].arguments;
}
- if (p_class->name != StringName()) {
+ if (!p_class->owner) {
parsed_classes.insert(p_class->name);
}
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index ec346a6b46..bae3f48923 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -742,13 +742,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX);
+#ifdef DEBUG_ENABLED
if (src->get_type() != var_type) {
- err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
- "' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
- OPCODE_BREAK;
+ if (Variant::can_convert_strict(src->get_type(), var_type)) {
+ Variant::CallError ce;
+ *dst = Variant::construct(var_type, const_cast<const Variant **>(&src), 1, ce);
+ } else {
+ err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
+ "' to a variable of type '" + Variant::get_type_name(var_type) + "'.";
+ OPCODE_BREAK;
+ }
+ } else {
+#endif // DEBUG_ENABLED
+ *dst = *src;
+#ifdef DEBUG_ENABLED
}
-
- *dst = *src;
+#endif // DEBUG_ENABLED
ip += 4;
}
@@ -761,17 +770,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_VARIANT_PTR(dst, 2);
GET_VARIANT_PTR(src, 3);
+#ifdef DEBUG_ENABLED
GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *());
GD_ERR_BREAK(!nc);
+ if (!src->get_type() != Variant::OBJECT && !src->get_type() != Variant::NIL) {
+ err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) +
+ "' to a variable of type '" + nc->get_name() + "'.";
+ OPCODE_BREAK;
+ }
Object *src_obj = src->operator Object *();
- GD_ERR_BREAK(!src_obj);
- if (!ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
+ if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) {
err_text = "Trying to assign value of type '" + src_obj->get_class_name() +
"' to a variable of type '" + nc->get_name() + "'.";
OPCODE_BREAK;
}
-
+#endif // DEBUG_ENABLED
*dst = *src;
ip += 4;
@@ -785,6 +799,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_VARIANT_PTR(dst, 2);
GET_VARIANT_PTR(src, 3);
+#ifdef DEBUG_ENABLED
Script *base_type = Object::cast_to<Script>(type->operator Object *());
GD_ERR_BREAK(!base_type);
@@ -820,6 +835,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK;
}
}
+#endif // DEBUG_ENABLED
*dst = *src;
@@ -1579,7 +1595,7 @@ StringName GDScriptFunction::get_global_name(int p_idx) const {
int GDScriptFunction::get_default_argument_count() const {
- return default_arguments.size();
+ return _default_arg_count;
}
int GDScriptFunction::get_default_argument_addr(int p_idx) const {
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index 63286b2e91..f2e52d48dd 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -1412,7 +1412,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) {
MethodInfo GDScriptFunctions::get_info(Function p_func) {
-#ifdef TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
//using a switch, so the compiler generates a jumptable
switch (p_func) {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index d8ce6e7f17..852d465206 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3332,6 +3332,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
switch (token) {
+ case GDScriptTokenizer::TK_CURSOR: {
+ tokenizer->advance();
+ } break;
case GDScriptTokenizer::TK_EOF:
p_class->end_line = tokenizer->get_token_line();
case GDScriptTokenizer::TK_ERROR: {
@@ -3552,7 +3555,10 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
DataType argtype;
if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
- if (!_parse_type(argtype)) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ argtype.infer_type = true;
+ tokenizer->advance();
+ } else if (!_parse_type(argtype)) {
_set_error("Expected type for argument.");
return;
}
@@ -4187,6 +4193,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
current_export.hint_string = native_class->get_name();
+ current_export.class_name = native_class->get_name();
} else {
current_export = PropertyInfo();
@@ -4546,6 +4553,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member._export.type = Variant::OBJECT;
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+ member._export.hint_string = member.data_type.native_type;
member._export.class_name = member.data_type.native_type;
} else {
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
@@ -5433,6 +5441,9 @@ GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Oper
if (b_type == Variant::INT || b_type == Variant::REAL) {
Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid);
}
+ if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
+ a = "%s"; // Work around for formatting operator (%)
+ }
Variant ret;
Variant::evaluate(p_op, a, b, ret, r_valid);
@@ -6770,7 +6781,26 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
// Check classes in current file
ClassNode *base = NULL;
if (!p_base_type) {
- // Possibly this is a global, check first
+ base = current_class;
+ base_type.has_type = true;
+ base_type.is_constant = true;
+ base_type.kind = DataType::CLASS;
+ base_type.class_type = base;
+ } else {
+ base_type = DataType(*p_base_type);
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ }
+ }
+
+ DataType member_type;
+
+ if (_get_member_type(base_type, p_identifier, member_type)) {
+ return member_type;
+ }
+
+ if (!p_base_type) {
+ // Possibly this is a global, check before failing
if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) {
DataType result;
@@ -6796,6 +6826,9 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
result.class_type = outer_class;
return result;
}
+ if (outer_class->constant_expressions.has(p_identifier)) {
+ return outer_class->constant_expressions[p_identifier].type;
+ }
for (int i = 0; i < outer_class->subclasses.size(); i++) {
if (outer_class->subclasses[i] == current_class) {
continue;
@@ -6885,27 +6918,6 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
}
}
- // Nothing found, keep looking in local scope
-
- base = current_class;
- base_type.has_type = true;
- base_type.is_constant = true;
- base_type.kind = DataType::CLASS;
- base_type.class_type = base;
- } else {
- base_type = *p_base_type;
- if (base_type.kind == DataType::CLASS) {
- base = base_type.class_type;
- }
- }
-
- DataType member_type;
-
- if (_get_member_type(base_type, p_identifier, member_type)) {
- return member_type;
- }
-
- if (!p_base_type) {
// This means looking in the current class, which type is always known
_set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
}
@@ -7131,11 +7143,9 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
// Arguments
int defaults_ofs = p_function->arguments.size() - p_function->default_values.size();
for (int i = 0; i < p_function->arguments.size(); i++) {
-
- // Resolve types
- p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
-
- if (i >= defaults_ofs) {
+ if (i < defaults_ofs) {
+ p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
+ } else {
if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) {
_set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column);
return;
@@ -7150,17 +7160,25 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
DataType def_type = _reduce_node_type(op->arguments[1]);
- if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
- String arg_name = p_function->arguments[i];
- _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
- arg_name + "' (" + p_function->arguments[i] + ")",
- p_function->line);
+ if (p_function->argument_types[i].infer_type) {
+ def_type.is_constant = false;
+ p_function->argument_types.write[i] = def_type;
+ } else {
+ p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
+
+ if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
+ String arg_name = p_function->arguments[i];
+ _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
+ arg_name + "' (" + p_function->arguments[i] + ")",
+ p_function->line);
+ }
}
}
}
if (!(p_function->name == "_init")) {
// Signature for the initializer may vary
+#ifdef DEBUG_ENABLED
DataType return_type;
List<DataType> arg_types;
int default_arg_count = 0;
@@ -7171,18 +7189,44 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) {
bool valid = _static == p_function->_static;
valid = valid && return_type == p_function->return_type;
- valid = valid && p_function->default_values.size() >= default_arg_count;
- valid = valid && arg_types.size() == p_function->arguments.size();
+ int argsize_diff = p_function->arguments.size() - arg_types.size();
+ valid = valid && argsize_diff >= 0;
+ valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff;
int i = 0;
for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) {
valid = valid && E->get() == p_function->argument_types[i++];
}
if (!valid) {
- _set_error("Function signature doesn't match the parent.", p_function->line);
+ String parent_signature = return_type.has_type ? return_type.to_string() : "Variant";
+ if (parent_signature == "null") {
+ parent_signature = "void";
+ }
+ parent_signature += " " + p_function->name + "(";
+ if (arg_types.size()) {
+ int i = 0;
+ for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) {
+ if (E != arg_types.front()) {
+ parent_signature += ", ";
+ }
+ String arg = E->get().to_string();
+ if (arg == "null" || arg == "var") {
+ arg = "Variant";
+ }
+ parent_signature += arg;
+ if (i == arg_types.size() - default_arg_count) {
+ parent_signature += "=default";
+ }
+
+ i++;
+ }
+ }
+ parent_signature += ")";
+ _set_error("Function signature doesn't match the parent. Parent signature is: '" + parent_signature + "'.", p_function->line);
return;
}
}
+#endif // DEBUG_ENABLED
} else {
if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
_set_error("Constructor cannot return a value.", p_function->line);
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index d29104ed45..ecf220a623 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -1295,7 +1295,7 @@ void VisualScriptEditor::_on_nodes_duplicate() {
Ref<VisualScriptNode> node = script->get_node(edited_func, F->get());
- Ref<VisualScriptNode> dupe = node->duplicate();
+ Ref<VisualScriptNode> dupe = node->duplicate(true);
int new_id = idc++;
to_select.insert(new_id);
@@ -3170,7 +3170,7 @@ void VisualScriptEditor::_menu_option(int p_what) {
return;
}
if (node.is_valid()) {
- clipboard->nodes[id] = node->duplicate();
+ clipboard->nodes[id] = node->duplicate(true);
clipboard->nodes_positions[id] = script->get_node_position(edited_func, id);
}
}
diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp
index 02a9e4d69b..e6a4b01deb 100644
--- a/scene/resources/audio_stream_sample.cpp
+++ b/scene/resources/audio_stream_sample.cpp
@@ -29,6 +29,8 @@
/*************************************************************************/
#include "audio_stream_sample.h"
+#include "io/marshalls.h"
+#include "os/file_access.h"
void AudioStreamPlaybackSample::start(float p_from_pos) {
@@ -509,6 +511,76 @@ PoolVector<uint8_t> AudioStreamSample::get_data() const {
return pv;
}
+void AudioStreamSample::save_to_wav(String p_path) {
+ if (format == AudioStreamSample::FORMAT_IMA_ADPCM) {
+ WARN_PRINTS("Saving IMA_ADPC samples are not supported yet");
+ return;
+ }
+
+ int sub_chunk_2_size = data_bytes; //Subchunk2Size = Size of data in bytes
+
+ // Format code
+ // 1:PCM format (for 8 or 16 bit)
+ // 3:IEEE float format
+ int format_code = (format == FORMAT_IMA_ADPCM) ? 3 : 1;
+
+ int n_channels = stereo ? 2 : 1;
+
+ long sample_rate = mix_rate;
+
+ int byte_pr_sample = 0;
+ switch (format) {
+ case AudioStreamSample::FORMAT_8_BITS: byte_pr_sample = 1; break;
+ case AudioStreamSample::FORMAT_16_BITS: byte_pr_sample = 2; break;
+ case AudioStreamSample::FORMAT_IMA_ADPCM: byte_pr_sample = 4; break;
+ }
+
+ String file_path = p_path;
+ if (!(file_path.substr(file_path.length() - 4, 4) == ".wav")) {
+ file_path += ".wav";
+ }
+
+ Error err;
+ FileAccess *file = FileAccess::open(file_path, FileAccess::WRITE, &err); //Overrides existing file if present
+
+ // Create WAV Header
+ file->store_string("RIFF"); //ChunkID
+ file->store_32(sub_chunk_2_size + 36); //ChunkSize = 36 + SubChunk2Size (size of entire file minus the 8 bits for this and previous header)
+ file->store_string("WAVE"); //Format
+ file->store_string("fmt "); //Subchunk1ID
+ file->store_32(16); //Subchunk1Size = 16
+ file->store_16(format_code); //AudioFormat
+ file->store_16(n_channels); //Number of Channels
+ file->store_32(sample_rate); //SampleRate
+ file->store_32(sample_rate * n_channels * byte_pr_sample); //ByteRate
+ file->store_16(n_channels * byte_pr_sample); //BlockAlign = NumChannels * BytePrSample
+ file->store_16(byte_pr_sample * 8); //BitsPerSample
+ file->store_string("data"); //Subchunk2ID
+ file->store_32(sub_chunk_2_size); //Subchunk2Size
+
+ // Add data
+ PoolVector<uint8_t>::Read read_data = get_data().read();
+ switch (format) {
+ case AudioStreamSample::FORMAT_8_BITS:
+ for (int i = 0; i < data_bytes; i++) {
+ uint8_t data_point = (read_data[i] + 128);
+ file->store_8(data_point);
+ }
+ break;
+ case AudioStreamSample::FORMAT_16_BITS:
+ for (int i = 0; i < data_bytes / 2; i++) {
+ uint16_t data_point = decode_uint16(&read_data[i * 2]);
+ file->store_16(data_point);
+ }
+ break;
+ case AudioStreamSample::FORMAT_IMA_ADPCM:
+ //Unimplemented
+ break;
+ }
+
+ file->close();
+}
+
Ref<AudioStreamPlayback> AudioStreamSample::instance_playback() {
Ref<AudioStreamPlaybackSample> sample;
@@ -545,6 +617,8 @@ void AudioStreamSample::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stereo", "stereo"), &AudioStreamSample::set_stereo);
ClassDB::bind_method(D_METHOD("is_stereo"), &AudioStreamSample::is_stereo);
+ ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamSample::save_to_wav);
+
ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong"), "set_loop_mode", "get_loop_mode");
diff --git a/scene/resources/audio_stream_sample.h b/scene/resources/audio_stream_sample.h
index 5fe65c194e..a27acc92b7 100644
--- a/scene/resources/audio_stream_sample.h
+++ b/scene/resources/audio_stream_sample.h
@@ -140,6 +140,8 @@ public:
void set_data(const PoolVector<uint8_t> &p_data);
PoolVector<uint8_t> get_data() const;
+ void save_to_wav(String p_path);
+
virtual Ref<AudioStreamPlayback> instance_playback();
virtual String get_stream_name() const;
diff --git a/servers/audio/audio_effect.h b/servers/audio/audio_effect.h
index cf732d4bdd..b950e824c0 100644
--- a/servers/audio/audio_effect.h
+++ b/servers/audio/audio_effect.h
@@ -39,6 +39,7 @@ class AudioEffectInstance : public Reference {
public:
virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) = 0;
+ virtual bool process_silence() const { return false; }
};
class AudioEffect : public Resource {
diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp
new file mode 100644
index 0000000000..ad5fad8464
--- /dev/null
+++ b/servers/audio/effects/audio_effect_record.cpp
@@ -0,0 +1,259 @@
+/*************************************************************************/
+/* audio_effect_record.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "audio_effect_record.h"
+
+void AudioEffectRecordInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
+ if (!is_recording) {
+ return;
+ }
+
+ //Add incoming audio frames to the IO ring buffer
+ const AudioFrame *src = p_src_frames;
+ AudioFrame *rb_buf = ring_buffer.ptrw();
+ for (int i = 0; i < p_frame_count; i++) {
+ rb_buf[ring_buffer_pos & ring_buffer_mask] = src[i];
+ ring_buffer_pos++;
+ }
+}
+
+bool AudioEffectRecordInstance::process_silence() {
+ return true;
+}
+
+void AudioEffectRecordInstance::_io_thread_process() {
+
+ //Reset recorder status
+ thread_active = true;
+ ring_buffer_pos = 0;
+ ring_buffer_read_pos = 0;
+
+ //We start a new recording
+ recording_data.resize(0); //Clear data completely and reset length
+ is_recording = true;
+
+ while (is_recording) {
+ //Check: The current recording has been requested to stop
+ if (is_recording && !base->should_record) {
+ is_recording = false;
+ }
+
+ //Case: Frames are remaining in the buffer
+ if (ring_buffer_read_pos < ring_buffer_pos) {
+ //Read from the buffer into recording_data
+ _io_store_buffer();
+ }
+ //Case: The buffer is empty
+ else if (is_recording) {
+ //Wait to avoid too much busy-wait
+ OS::get_singleton()->delay_usec(500);
+ }
+ }
+
+ thread_active = false;
+}
+
+void AudioEffectRecordInstance::_io_store_buffer() {
+ int to_read = ring_buffer_pos - ring_buffer_read_pos;
+
+ AudioFrame *rb_buf = ring_buffer.ptrw();
+
+ while (to_read) {
+ AudioFrame buffered_frame = rb_buf[ring_buffer_read_pos & ring_buffer_mask];
+ recording_data.push_back(buffered_frame.l);
+ recording_data.push_back(buffered_frame.r);
+
+ ring_buffer_read_pos++;
+ to_read--;
+ }
+}
+
+void AudioEffectRecordInstance::_thread_callback(void *_instance) {
+
+ AudioEffectRecordInstance *aeri = reinterpret_cast<AudioEffectRecordInstance *>(_instance);
+
+ aeri->_io_thread_process();
+}
+
+void AudioEffectRecordInstance::init() {
+ io_thread = Thread::create(_thread_callback, this);
+}
+
+Ref<AudioEffectInstance> AudioEffectRecord::instance() {
+ Ref<AudioEffectRecordInstance> ins;
+ ins.instance();
+ ins->base = Ref<AudioEffectRecord>(this);
+ ins->is_recording = false;
+
+ //Re-using the buffer size calculations from audio_effect_delay.cpp
+ float ring_buffer_max_size = IO_BUFFER_SIZE_MS;
+ ring_buffer_max_size /= 1000.0; //convert to seconds
+ ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate();
+
+ int ringbuff_size = ring_buffer_max_size;
+
+ int bits = 0;
+
+ while (ringbuff_size > 0) {
+ bits++;
+ ringbuff_size /= 2;
+ }
+
+ ringbuff_size = 1 << bits;
+ ins->ring_buffer_mask = ringbuff_size - 1;
+ ins->ring_buffer_pos = 0;
+
+ ins->ring_buffer.resize(ringbuff_size);
+
+ ins->ring_buffer_read_pos = 0;
+
+ ensure_thread_stopped();
+ current_instance = ins;
+ if (should_record) {
+ ins->init();
+ }
+
+ return ins;
+}
+
+void AudioEffectRecord::ensure_thread_stopped() {
+ should_record = false;
+ if (current_instance != 0 && current_instance->thread_active) {
+ Thread::wait_to_finish(current_instance->io_thread);
+ }
+}
+
+void AudioEffectRecord::set_should_record(bool p_record) {
+ if (p_record) {
+ ensure_thread_stopped();
+ current_instance->init();
+ }
+
+ should_record = p_record;
+}
+
+bool AudioEffectRecord::get_should_record() const {
+ return should_record;
+}
+
+void AudioEffectRecord::set_format(AudioStreamSample::Format p_format) {
+ format = p_format;
+}
+
+AudioStreamSample::Format AudioEffectRecord::get_format() const {
+ return format;
+}
+
+Ref<AudioStreamSample> AudioEffectRecord::get_recording() const {
+ AudioStreamSample::Format dst_format = format;
+ bool stereo = true; //forcing mono is not implemented
+
+ PoolVector<uint8_t> dst_data;
+
+ if (dst_format == AudioStreamSample::FORMAT_8_BITS) {
+ int data_size = current_instance->recording_data.size();
+ dst_data.resize(data_size);
+ PoolVector<uint8_t>::Write w = dst_data.write();
+
+ for (int i = 0; i < data_size; i++) {
+ int8_t v = CLAMP(current_instance->recording_data[i] * 128, -128, 127);
+ w[i] = v;
+ }
+ } else if (dst_format == AudioStreamSample::FORMAT_16_BITS) {
+ int data_size = current_instance->recording_data.size();
+ dst_data.resize(data_size * 2);
+ PoolVector<uint8_t>::Write w = dst_data.write();
+
+ for (int i = 0; i < data_size; i++) {
+ int16_t v = CLAMP(current_instance->recording_data[i] * 32768, -32768, 32767);
+ encode_uint16(v, &w[i * 2]);
+ }
+ } else if (dst_format == AudioStreamSample::FORMAT_IMA_ADPCM) {
+ //byte interleave
+ Vector<float> left;
+ Vector<float> right;
+
+ int tframes = current_instance->recording_data.size() / 2;
+ left.resize(tframes);
+ right.resize(tframes);
+
+ for (int i = 0; i < tframes; i++) {
+ left.set(i, current_instance->recording_data[i * 2 + 0]);
+ right.set(i, current_instance->recording_data[i * 2 + 1]);
+ }
+
+ PoolVector<uint8_t> bleft;
+ PoolVector<uint8_t> bright;
+
+ ResourceImporterWAV::_compress_ima_adpcm(left, bleft);
+ ResourceImporterWAV::_compress_ima_adpcm(right, bright);
+
+ int dl = bleft.size();
+ dst_data.resize(dl * 2);
+
+ PoolVector<uint8_t>::Write w = dst_data.write();
+ PoolVector<uint8_t>::Read rl = bleft.read();
+ PoolVector<uint8_t>::Read rr = bright.read();
+
+ for (int i = 0; i < dl; i++) {
+ w[i * 2 + 0] = rl[i];
+ w[i * 2 + 1] = rr[i];
+ }
+ } else {
+ ERR_EXPLAIN("format not implemented");
+ }
+
+ Ref<AudioStreamSample> sample;
+ sample.instance();
+ sample->set_data(dst_data);
+ sample->set_format(dst_format);
+ sample->set_mix_rate(AudioServer::get_singleton()->get_mix_rate());
+ sample->set_loop_mode(AudioStreamSample::LOOP_DISABLED);
+ sample->set_loop_begin(0);
+ sample->set_loop_end(0);
+ sample->set_stereo(stereo);
+
+ return sample;
+}
+
+void AudioEffectRecord::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_should_record", "record"), &AudioEffectRecord::set_should_record);
+ ClassDB::bind_method(D_METHOD("get_should_record"), &AudioEffectRecord::get_should_record);
+ ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioEffectRecord::set_format);
+ ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format);
+ ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "should_record"), "set_should_record", "get_should_record");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
+}
+
+AudioEffectRecord::AudioEffectRecord() {
+ format = AudioStreamSample::FORMAT_16_BITS;
+}
diff --git a/servers/audio/effects/audio_effect_record.h b/servers/audio/effects/audio_effect_record.h
new file mode 100644
index 0000000000..05c2d7352f
--- /dev/null
+++ b/servers/audio/effects/audio_effect_record.h
@@ -0,0 +1,103 @@
+/*************************************************************************/
+/* audio_effect_record.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef AUDIOEFFECTRECORD_H
+#define AUDIOEFFECTRECORD_H
+
+#include "core/os/thread.h"
+#include "editor/import/resource_importer_wav.h"
+#include "io/marshalls.h"
+#include "os/file_access.h"
+#include "os/os.h"
+#include "scene/resources/audio_stream_sample.h"
+#include "servers/audio/audio_effect.h"
+#include "servers/audio_server.h"
+
+class AudioEffectRecord;
+
+class AudioEffectRecordInstance : public AudioEffectInstance {
+ GDCLASS(AudioEffectRecordInstance, AudioEffectInstance)
+ friend class AudioEffectRecord;
+ Ref<AudioEffectRecord> base;
+
+ bool is_recording;
+ Thread *io_thread;
+ bool thread_active = false;
+
+ Vector<AudioFrame> ring_buffer;
+ Vector<float> recording_data;
+
+ unsigned int ring_buffer_pos;
+ unsigned int ring_buffer_mask;
+ unsigned int ring_buffer_read_pos;
+
+ void _io_thread_process();
+ void _io_store_buffer();
+ static void _thread_callback(void *_instance);
+ void _init_recording();
+
+public:
+ void init();
+ virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count);
+ virtual bool process_silence();
+};
+
+class AudioEffectRecord : public AudioEffect {
+ GDCLASS(AudioEffectRecord, AudioEffect)
+
+ friend class AudioEffectRecordInstance;
+
+ enum {
+ IO_BUFFER_SIZE_MS = 1500
+ };
+
+ bool should_record;
+ Ref<AudioEffectRecordInstance> current_instance;
+
+ AudioStreamSample::Format format;
+
+ void ensure_thread_stopped();
+
+protected:
+ static void _bind_methods();
+ static void debug(uint64_t time_diff, int p_frame_count);
+
+public:
+ Ref<AudioEffectInstance> instance();
+ void set_should_record(bool p_record);
+ bool get_should_record() const;
+ void set_format(AudioStreamSample::Format p_format);
+ AudioStreamSample::Format get_format() const;
+ Ref<AudioStreamSample> get_recording() const;
+
+ AudioEffectRecord();
+};
+
+#endif // AUDIOEFFECTRECORD_H
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 2c81ac32f0..2eaa2ce8e7 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -332,7 +332,7 @@ void AudioServer::_mix_step() {
for (int k = 0; k < bus->channels.size(); k++) {
- if (!bus->channels[k].active)
+ if (!(bus->channels[k].active || bus->channels[k].effect_instances[j]->process_silence()))
continue;
bus->channels.write[k].effect_instances.write[j]->process(bus->channels[k].buffer.ptr(), temp_buffer.write[k].ptrw(), buffer_size);
}
@@ -340,7 +340,7 @@ void AudioServer::_mix_step() {
//swap buffers, so internal buffer always has the right data
for (int k = 0; k < bus->channels.size(); k++) {
- if (!buses[i]->channels[k].active)
+ if (!(buses[i]->channels[k].active || bus->channels[k].effect_instances[j]->process_silence()))
continue;
SWAP(bus->channels.write[k].buffer, temp_buffer.write[k]);
}
diff --git a/servers/physics/body_sw.h b/servers/physics/body_sw.h
index 8f5e6d8251..5df270f679 100644
--- a/servers/physics/body_sw.h
+++ b/servers/physics/body_sw.h
@@ -442,6 +442,9 @@ public:
ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, Vector3());
return body->contacts[p_contact_idx].local_normal;
}
+ virtual float get_contact_impulse(int p_contact_idx) const {
+ return 0.0f; // Only implemented for bullet
+ }
virtual int get_contact_local_shape(int p_contact_idx) const {
ERR_FAIL_INDEX_V(p_contact_idx, body->contact_count, -1);
return body->contacts[p_contact_idx].local_shape;
diff --git a/servers/physics/collision_solver_sat.cpp b/servers/physics/collision_solver_sat.cpp
index 44b7c9ac34..8f2b147460 100644
--- a/servers/physics/collision_solver_sat.cpp
+++ b/servers/physics/collision_solver_sat.cpp
@@ -348,7 +348,9 @@ public:
//use the smallest depth
- min_B = -min_B;
+ if (min_B < 0.0) { // could be +0.0, we don't want it to become -0.0
+ min_B = -min_B;
+ }
if (max_B < min_B) {
if (max_B < best_depth) {
diff --git a/servers/physics_server.cpp b/servers/physics_server.cpp
index 11b286d8ef..cda3856ecc 100644
--- a/servers/physics_server.cpp
+++ b/servers/physics_server.cpp
@@ -106,6 +106,7 @@ void PhysicsDirectBodyState::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_contact_local_position", "contact_idx"), &PhysicsDirectBodyState::get_contact_local_position);
ClassDB::bind_method(D_METHOD("get_contact_local_normal", "contact_idx"), &PhysicsDirectBodyState::get_contact_local_normal);
+ ClassDB::bind_method(D_METHOD("get_contact_impulse", "contact_idx"), &PhysicsDirectBodyState::get_contact_impulse);
ClassDB::bind_method(D_METHOD("get_contact_local_shape", "contact_idx"), &PhysicsDirectBodyState::get_contact_local_shape);
ClassDB::bind_method(D_METHOD("get_contact_collider", "contact_idx"), &PhysicsDirectBodyState::get_contact_collider);
ClassDB::bind_method(D_METHOD("get_contact_collider_position", "contact_idx"), &PhysicsDirectBodyState::get_contact_collider_position);
diff --git a/servers/physics_server.h b/servers/physics_server.h
index 217656e2a9..294c6b6674 100644
--- a/servers/physics_server.h
+++ b/servers/physics_server.h
@@ -77,6 +77,7 @@ public:
virtual Vector3 get_contact_local_position(int p_contact_idx) const = 0;
virtual Vector3 get_contact_local_normal(int p_contact_idx) const = 0;
+ virtual float get_contact_impulse(int p_contact_idx) const = 0;
virtual int get_contact_local_shape(int p_contact_idx) const = 0;
virtual RID get_contact_collider(int p_contact_idx) const = 0;
diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp
index 1bad7e652b..aa0e5c289b 100644
--- a/servers/register_server_types.cpp
+++ b/servers/register_server_types.cpp
@@ -48,6 +48,7 @@
#include "audio/effects/audio_effect_panner.h"
#include "audio/effects/audio_effect_phaser.h"
#include "audio/effects/audio_effect_pitch_shift.h"
+#include "audio/effects/audio_effect_record.h"
#include "audio/effects/audio_effect_reverb.h"
#include "audio/effects/audio_effect_stereo_enhance.h"
#include "audio_server.h"
@@ -138,6 +139,7 @@ void register_server_types() {
ClassDB::register_class<AudioEffectLimiter>();
ClassDB::register_class<AudioEffectPitchShift>();
ClassDB::register_class<AudioEffectPhaser>();
+ ClassDB::register_class<AudioEffectRecord>();
}
ClassDB::register_virtual_class<Physics2DDirectBodyState>();