diff options
30 files changed, 995 insertions, 604 deletions
diff --git a/core/project_settings.cpp b/core/project_settings.cpp index a74917162b..23e4961138 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -114,7 +114,15 @@ String ProjectSettings::globalize_path(const String &p_path) const { return p_path.replace("res:/", resource_path); }; return p_path.replace("res://", ""); - }; + } else if (p_path.begins_with("user://")) { + + String data_dir = OS::get_singleton()->get_data_dir(); + if (data_dir != "") { + + return p_path.replace("user:/", data_dir); + }; + return p_path.replace("user://", ""); + } return p_path; } diff --git a/doc/base/classes.xml b/doc/base/classes.xml index 69b3628ce7..9c3b8a2704 100644 --- a/doc/base/classes.xml +++ b/doc/base/classes.xml @@ -53424,10 +53424,10 @@ </class> <class name="Transform" category="Built-In Types"> <brief_description> - 3D Transformation. + 3D Transformation. 3x4 matrix. </brief_description> <description> - Transform is used to store translation, rotation and scaling transformations. It consists of a Basis "basis" and Vector3 "origin". Transform is used to represent transformations of objects in space, and as such, determine their position, orientation and scale. It is similar to a 3x4 matrix. + Represents one or many transformations in 3D space such as translation, rotation, or scaling. It consists of a [Basis] "basis" and an [Vector3] "origin". It is similar to a 3x4 matrix. </description> <methods> <method name="Transform"> @@ -53442,7 +53442,7 @@ <argument index="3" name="origin" type="Vector3"> </argument> <description> - Construct the Transform from four Vector3. Each axis corresponds to local basis vectors (some of which may be scaled). + Construct the Transform from four [Vector3]. Each axis corresponds to local basis vectors (some of which may be scaled). </description> </method> <method name="Transform"> @@ -53453,7 +53453,7 @@ <argument index="1" name="origin" type="Vector3"> </argument> <description> - Construct the Transform from a Basis and Vector3. + Construct the Transform from a [Basis] and [Vector3]. </description> </method> <method name="Transform"> @@ -53462,7 +53462,7 @@ <argument index="0" name="from" type="Transform2D"> </argument> <description> - Construct the Transform from a Transform2D. + Construct the Transform from a [Transform2D]. </description> </method> <method name="Transform"> @@ -53471,7 +53471,7 @@ <argument index="0" name="from" type="Quat"> </argument> <description> - Construct the Transform from a Quat. The origin will be Vector3(0, 0, 0). + Construct the Transform from a [Quat]. The origin will be Vector3(0, 0, 0). </description> </method> <method name="Transform"> @@ -53480,7 +53480,7 @@ <argument index="0" name="from" type="Basis"> </argument> <description> - Construct the Transform from a Basis. The origin will be Vector3(0, 0, 0). + Construct the Transform from a [Basis]. The origin will be Vector3(0, 0, 0). </description> </method> <method name="affine_inverse"> @@ -53498,13 +53498,14 @@ <argument index="1" name="weight" type="float"> </argument> <description> + Interpolate to other Transform by weight amount (0-1). </description> </method> <method name="inverse"> <return type="Transform"> </return> <description> - Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling). + Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use affine_inverse for transforms with scaling). </description> </method> <method name="looking_at"> @@ -53551,7 +53552,7 @@ <argument index="0" name="ofs" type="Vector3"> </argument> <description> - Translate the transform by the specified displacement. + Translate the transform by the specified offset. </description> </method> <method name="xform"> @@ -53575,10 +53576,10 @@ </methods> <members> <member name="basis" type="Basis" setter="" getter="" brief=""> - The basis is a matrix containing 3 [Vector3] as its columns: X axis, Y axis, and Z axis. These vectors can be interpreted as the basis vectors of local coordinate system travelling with the object. + The basis is a matrix containing 3 [Vector3] as its columns: X axis, Y axis, and Z axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object. </member> <member name="origin" type="Vector3" setter="" getter="" brief=""> - The origin of the transform. Which is the translation offset. + The translation offset of the transform. </member> </members> <constants> @@ -53586,10 +53587,10 @@ </class> <class name="Transform2D" category="Built-In Types"> <brief_description> - 3x2 Matrix for 2D transforms. + 2D Transformation. 3x2 matrix. </brief_description> <description> - 3x2 Matrix for 2D transforms. + Represents one or many transformations in 3D space such as translation, rotation, or scaling. It consists of a two [Vector2] x, y and [Vector2] "origin". It is similar to a 3x2 matrix. </description> <methods> <method name="Transform2D"> @@ -53598,6 +53599,7 @@ <argument index="0" name="from" type="Transform"> </argument> <description> + Constructs the [Transform2D] from a 3D [Transform]. </description> </method> <method name="Transform2D"> @@ -53610,6 +53612,7 @@ <argument index="2" name="origin" type="Vector2"> </argument> <description> + Constructs the [Transform2D] from 3 [Vector2] consisting of rows x, y and origin. </description> </method> <method name="Transform2D"> @@ -53620,13 +53623,14 @@ <argument index="1" name="pos" type="Vector2"> </argument> <description> + Constructs the [Transform2D] from rotation angle in radians and position [Vector2]. </description> </method> <method name="affine_inverse"> <return type="Transform2D"> </return> <description> - Return the inverse of the matrix. + Returns the inverse of the matrix. </description> </method> <method name="basis_xform"> @@ -53635,6 +53639,7 @@ <argument index="0" name="v" type="var"> </argument> <description> + Transforms the given vector "v" by this transform basis (no translation). </description> </method> <method name="basis_xform_inv"> @@ -53643,12 +53648,14 @@ <argument index="0" name="v" type="var"> </argument> <description> + Inverse-transforms vector "v" by this transform basis (no translation). </description> </method> <method name="get_origin"> <return type="Vector2"> </return> <description> + Return the origin [Vector2] (translation). </description> </method> <method name="get_rotation"> @@ -53662,6 +53669,7 @@ <return type="Vector2"> </return> <description> + Return the scale. </description> </method> <method name="interpolate_with"> @@ -53672,18 +53680,21 @@ <argument index="1" name="weight" type="float"> </argument> <description> + Interpolate to other Transform2D by weight amount (0-1). </description> </method> <method name="inverse"> <return type="Transform2D"> </return> <description> + Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use affine_inverse for transforms with scaling). </description> </method> <method name="orthonormalized"> <return type="Transform2D"> </return> <description> + Returns a transfrom with the basis orthogonal (90 degrees), and normalized axis vectors. </description> </method> <method name="rotated"> @@ -53692,6 +53703,7 @@ <argument index="0" name="phi" type="float"> </argument> <description> + Rotate the transform by phi. </description> </method> <method name="scaled"> @@ -53700,6 +53712,7 @@ <argument index="0" name="scale" type="Vector2"> </argument> <description> + Scale the transform by the specified 2D scaling factors. </description> </method> <method name="translated"> @@ -53708,6 +53721,7 @@ <argument index="0" name="offset" type="Vector2"> </argument> <description> + Translate the transform by the specified offset. </description> </method> <method name="xform"> @@ -53716,6 +53730,7 @@ <argument index="0" name="v" type="var"> </argument> <description> + Transforms the given vector "v" by this transform. </description> </method> <method name="xform_inv"> @@ -53724,15 +53739,19 @@ <argument index="0" name="v" type="var"> </argument> <description> + Inverse-transforms the given vector "v" by this transform. </description> </method> </methods> <members> <member name="origin" type="Vector2" setter="" getter="" brief=""> + The translation offset of the transform. </member> <member name="x" type="Vector2" setter="" getter="" brief=""> + The X axis of 2x2 basis matrix containing 2 [Vector2] as its columns: X axis and Y axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object. </member> <member name="y" type="Vector2" setter="" getter="" brief=""> + The Y axis of 2x2 basis matrix containing 2 [Vector2] as its columns: X axis and Y axis. These vectors can be interpreted as the basis vectors of local coordinate system traveling with the object. </member> </members> <constants> diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index b85a63a44a..6b8038c3ef 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -221,37 +221,24 @@ Error DirAccessUnix::change_dir(String p_dir) { if (prev_dir.parse_utf8(real_current_dir_name)) prev_dir = real_current_dir_name; //no utf8, maybe latin? - //print_line("directory we are changing out of (prev_dir): " + prev_dir); - // try_dir is the directory we are trying to change into String try_dir = ""; if (p_dir.is_rel_path()) { String next_dir = current_dir + "/" + p_dir; - //print_line("p_dir is relative: " + p_dir + " about to simplfy: " + next_dir); next_dir = next_dir.simplify_path(); try_dir = next_dir; } else { try_dir = p_dir; - //print_line("p_dir is absolute: " + p_dir); - } - - // if try_dir is nothing, it is not changing directory so change it to a "." otherwise chdir will fail - if (try_dir == "") { - try_dir = "."; } - //print_line("directory we are changing in to (try_dir): " + try_dir); - bool worked = (chdir(try_dir.utf8().get_data()) == 0); // we can only give this utf8 if (!worked) { - //print_line("directory does not exist"); return ERR_INVALID_PARAMETER; } // the directory exists, so set current_dir to try_dir current_dir = try_dir; chdir(prev_dir.utf8().get_data()); - //print_line("directory exists, setting current_dir to: " + current_dir); return OK; } @@ -319,11 +306,16 @@ size_t DirAccessUnix::get_space_left() { DirAccessUnix::DirAccessUnix() { dir_stream = 0; - current_dir = "."; _cisdir = false; /* determine drive count */ + // set current directory to an absolute path of the current directory + char real_current_dir_name[2048]; + getcwd(real_current_dir_name, 2048); + if (current_dir.parse_utf8(real_current_dir_name)) + current_dir = real_current_dir_name; + change_dir(current_dir); } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 323a36154e..da53b06516 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -449,13 +449,15 @@ struct ProjectItem { String conf; uint64_t last_modified; bool favorite; + bool grayed; ProjectItem() {} - ProjectItem(const String &p_project, const String &p_path, const String &p_conf, uint64_t p_last_modified, bool p_favorite = false) { + ProjectItem(const String &p_project, const String &p_path, const String &p_conf, uint64_t p_last_modified, bool p_favorite = false, bool p_grayed = false) { project = p_project; path = p_path; conf = p_conf; last_modified = p_last_modified; favorite = p_favorite; + grayed = p_grayed; } _FORCE_INLINE_ bool operator<(const ProjectItem &l) const { return last_modified > l.last_modified; } _FORCE_INLINE_ bool operator==(const ProjectItem &l) const { return project == l.project; } @@ -737,6 +739,7 @@ void ProjectManager::_load_recent_projects() { String project = _name.get_slice("/", 1); String conf = path.plus_file("project.godot"); bool favorite = (_name.begins_with("favorite_projects/")) ? true : false; + bool grayed = false; uint64_t last_modified = 0; if (FileAccess::exists(conf)) { @@ -748,16 +751,15 @@ void ProjectManager::_load_recent_projects() { if (cache_modified > last_modified) last_modified = cache_modified; } - - ProjectItem item(project, path, conf, last_modified, favorite); - if (favorite) - favorite_projects.push_back(item); - else - projects.push_back(item); } else { - //project doesn't exist on disk but it's in the XML settings file - EditorSettings::get_singleton()->erase(_name); //remove it + grayed = true; } + + ProjectItem item(project, path, conf, last_modified, favorite, grayed); + if (favorite) + favorite_projects.push_back(item); + else + projects.push_back(item); } projects.sort(); @@ -782,14 +784,14 @@ void ProjectManager::_load_recent_projects() { String path = item.path; String conf = item.conf; bool is_favorite = item.favorite; + bool is_grayed = item.grayed; Ref<ConfigFile> cf = memnew(ConfigFile); - Error err = cf->load(conf); - ERR_CONTINUE(err != OK); + Error cf_err = cf->load(conf); String project_name = TTR("Unnamed Project"); - if (cf->has_section_key("application", "config/name")) { + if (cf_err == OK && cf->has_section_key("application", "config/name")) { project_name = static_cast<String>(cf->get_value("application", "config/name")).xml_unescape(); } @@ -797,7 +799,7 @@ void ProjectManager::_load_recent_projects() { continue; Ref<Texture> icon; - if (cf->has_section_key("application", "config/icon")) { + if (cf_err == OK && cf->has_section_key("application", "config/icon")) { String appicon = cf->get_value("application", "config/icon"); if (appicon != "") { Ref<Image> img; @@ -819,8 +821,10 @@ void ProjectManager::_load_recent_projects() { } String main_scene; - if (cf->has_section_key("application", "run/main_scene")) { + if (cf_err == OK && cf->has_section_key("application", "run/main_scene")) { main_scene = cf->get_value("application", "run/main_scene"); + } else { + main_scene = ""; } selected_list_copy.erase(project); @@ -848,6 +852,8 @@ void ProjectManager::_load_recent_projects() { hb->add_child(tf); VBoxContainer *vb = memnew(VBoxContainer); + if (is_grayed) + vb->set_modulate(Color(0.5, 0.5, 0.5)); vb->set_name("project"); vb->set_h_size_flags(SIZE_EXPAND_FILL); hb->add_child(vb); @@ -926,6 +932,13 @@ void ProjectManager::_open_project_confirm() { for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) { const String &selected = E->key(); String path = EditorSettings::get_singleton()->get("projects/" + selected); + String conf = path + "/project.godot"; + if (!FileAccess::exists(conf)) { + dialog_error->set_text(TTR("Can't open project")); + dialog_error->popup_centered_minsize(); + return; + } + print_line("OPENING: " + path + " (" + selected + ")"); List<String> args; @@ -1397,6 +1410,9 @@ ProjectManager::ProjectManager() { run_error_diag = memnew(AcceptDialog); gui_base->add_child(run_error_diag); run_error_diag->set_title(TTR("Can't run project")); + + dialog_error = memnew(AcceptDialog); + gui_base->add_child(dialog_error); } ProjectManager::~ProjectManager() { diff --git a/editor/project_manager.h b/editor/project_manager.h index ecc01955ba..9fcf10a0d9 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -58,6 +58,7 @@ class ProjectManager : public Control { ConfirmationDialog *multi_run_ask; ConfirmationDialog *multi_scan_ask; AcceptDialog *run_error_diag; + AcceptDialog *dialog_error; NewProjectDialog *npdialog; ScrollContainer *scroll; VBoxContainer *scroll_childs; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index afdf48b314..4fe24a2eff 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -823,7 +823,14 @@ Node *SceneTreeDock::_duplicate(Node *p_node, Map<Node *, Node *> &duplimap) { if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; String name = E->get().name; - node->set(name, p_node->get(name)); + Variant value = p_node->get(name); + // Duplicate dictionaries and arrays, mainly needed for __meta__ + if (value.get_type() == Variant::DICTIONARY) { + value = Dictionary(value).copy(); + } else if (value.get_type() == Variant::ARRAY) { + value = Array(value).duplicate(); + } + node->set(name, value); } List<Node::GroupInfo> group_info; diff --git a/misc/dist/html/default.html b/misc/dist/html/default.html new file mode 100644 index 0000000000..9fae34f97e --- /dev/null +++ b/misc/dist/html/default.html @@ -0,0 +1,386 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> +<head> + <meta charset="utf-8" /> + <title></title> + <style type="text/css"> + + body { + margin: 0; + border: 0 none; + padding: 0; + text-align: center; + background-color: #222226; + font-family: 'Noto Sans', Arial, sans-serif; + } + + + /* Godot Engine default theme style + * ================================ */ + + .godot { + color: #e0e0e0; + background-color: #3b3943; + background-image: linear-gradient(to bottom, #403e48, #35333c); + border: 1px solid #45434e; + box-shadow: 0 0 1px 1px #2f2d35; + } + + button.godot { + font-family: 'Droid Sans', Arial, sans-serif; /* override user agent style */ + padding: 1px 5px; + background-color: #37353f; + background-image: linear-gradient(to bottom, #413e49, #3a3842); + border: 1px solid #514f5d; + border-radius: 1px; + box-shadow: 0 0 1px 1px #2a2930; + } + + button.godot:hover { + color: #f0f0f0; + background-color: #44414e; + background-image: linear-gradient(to bottom, #494652, #423f4c); + border: 1px solid #5a5667; + box-shadow: 0 0 1px 1px #26252b; + } + + button.godot:active { + color: #fff; + background-color: #3e3b46; + background-image: linear-gradient(to bottom, #36343d, #413e49); + border: 1px solid #4f4c59; + box-shadow: 0 0 1px 1px #26252b; + } + + button.godot:disabled { + color: rgba(230, 230, 230, 0.2); + background-color: #3d3d3d; + background-image: linear-gradient(to bottom, #434343, #393939); + border: 1px solid #474747; + box-shadow: 0 0 1px 1px #2d2b33; + } + + + /* Canvas / wrapper + * ================ */ + + #container { + display: inline-block; /* scale with canvas */ + vertical-align: top; /* prevent extra height */ + position: relative; /* root for absolutely positioned overlay */ + margin: 0; + border: 0 none; + padding: 0; + background-color: #0c0c0c; + } + + #canvas { + display: block; + margin: 0 auto; + color: white; + } + + #canvas:focus { + outline: none; + } + + + /* Status display + * ============== */ + + #status { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + /* don't consume click events - make children visible explicitly */ + visibility: hidden; + } + + #status-progress { + width: 244px; + height: 7px; + background-color: #38363A; + border: 1px solid #444246; + padding: 1px; + box-shadow: 0 0 2px 1px #1B1C22; + border-radius: 2px; + visibility: visible; + } + + #status-progress-inner { + height: 100%; + width: 0; + box-sizing: border-box; + transition: width 0.5s linear; + background-color: #202020; + border: 1px solid #222223; + box-shadow: 0 0 1px 1px #27282E; + border-radius: 3px; + } + + #status-indeterminate { + visibility: visible; + position: relative; + } + + #status-indeterminate > div { + width: 3px; + height: 0; + border-style: solid; + border-width: 6px 2px 0 2px; + border-color: #2b2b2b transparent transparent transparent; + transform-origin: center 14px; + position: absolute; + } + + #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); } + #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); } + #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); } + #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); } + #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); } + #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); } + #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); } + #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); } + + #status-notice { + margin: 0 100px; + line-height: 1.3; + visibility: visible; + padding: 4px 6px; + visibility: visible; + } + + + /* Debug output + * ============ */ + + #output-panel { + display: none; + max-width: 700px; + font-size: small; + margin: 6px auto 0; + padding: 0 4px 4px; + text-align: left; + line-height: 2.2; + } + + #output-header { + display: flex; + justify-content: space-between; + align-items: center; + } + + #output-container { + padding: 6px; + background-color: #2c2a32; + box-shadow: inset 0 0 1px 1px #232127; + color: #bbb; + } + + #output-scroll { + line-height: 1; + height: 12em; + overflow-y: scroll; + white-space: pre-wrap; + font-size: small; + font-family: "Lucida Console", Monaco, monospace; + } + </style> +$GODOT_HEAD_INCLUDE +</head> +<body> + <div id="container"> + <canvas id="canvas" oncontextmenu="event.preventDefault();" width="640" height="480"> + HTML5 canvas appears to be unsupported in the current browser.<br /> + Please try updating or use a different browser. + </canvas> + <div id="status"> + <div id='status-progress' style='display: none;' oncontextmenu="event.preventDefault();"><div id ='status-progress-inner'></div></div> + <div id='status-indeterminate' style='display: none;' oncontextmenu="event.preventDefault();"> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + <div id="status-notice" class="godot" style='display: none;'></div> + </div> + </div> + <div id="output-panel" class="godot"> + <div id="output-header"> + Output: + <button id='output-clear' class='godot' type='button' autocomplete='off'>Clear</button> + </div> + <div id="output-container"><div id="output-scroll"></div></div> + </div> + + <script type="text/javascript" src="$GODOT_BASENAME.js"></script> + <script type="text/javascript">//<![CDATA[ + + var game = new Engine; + + (function() { + + const BASENAME = '$GODOT_BASENAME'; + const MEMORY_SIZE = $GODOT_TOTAL_MEMORY; + const DEBUG_ENABLED = $GODOT_DEBUG_ENABLED; + const INDETERMINATE_STATUS_STEP_MS = 100; + + var container = document.getElementById('container'); + var canvas = document.getElementById('canvas'); + var statusProgress = document.getElementById('status-progress'); + var statusProgressInner = document.getElementById('status-progress-inner'); + var statusIndeterminate = document.getElementById('status-indeterminate'); + var statusNotice = document.getElementById('status-notice'); + + var initializing = true; + var statusMode = 'hidden'; + var indeterminiateStatusAnimationId = 0; + + setStatusMode('indeterminate'); + game.setCanvas(canvas); + game.setAsmjsMemorySize(MEMORY_SIZE); + + function setStatusMode(mode) { + + if (statusMode === mode || !initializing) + return; + [statusProgress, statusIndeterminate, statusNotice].forEach(elem => { + elem.style.display = 'none'; + }); + if (indeterminiateStatusAnimationId !== 0) { + cancelAnimationFrame(indeterminiateStatusAnimationId); + indeterminiateStatusAnimationId = 0; + } + switch (mode) { + case 'progress': + statusProgress.style.display = 'block'; + break; + case 'indeterminate': + statusIndeterminate.style.display = 'block'; + indeterminiateStatusAnimationId = requestAnimationFrame(animateStatusIndeterminate); + break; + case 'notice': + statusNotice.style.display = 'block'; + break; + case 'hidden': + break; + default: + throw new Error("Invalid status mode"); + } + statusMode = mode; + } + + function animateStatusIndeterminate(ms) { + var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8); + if (statusIndeterminate.children[i].style.borderTopColor == '') { + Array.prototype.slice.call(statusIndeterminate.children).forEach(child => { + child.style.borderTopColor = ''; + }); + statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf'; + } + requestAnimationFrame(animateStatusIndeterminate); + } + + function setStatusNotice(text) { + + while (statusNotice.lastChild) { + statusNotice.removeChild(statusNotice.lastChild); + } + var lines = text.split('\n'); + lines.forEach((line, index) => { + statusNotice.appendChild(document.createTextNode(line)); + statusNotice.appendChild(document.createElement('br')); + }); + }; + + game.setProgressFunc((current, total) => { + + if (total > 0) { + statusProgressInner.style.width = current/total * 100 + '%'; + setStatusMode('progress'); + if (current === total) { + // wait for progress bar animation + setTimeout(() => { + setStatusMode('indeterminate'); + }, 500); + } + } else { + setStatusMode('indeterminate'); + } + }); + + if (DEBUG_ENABLED) { + var outputRoot = document.getElementById("output-panel"); + var outputScroll = document.getElementById("output-scroll"); + var OUTPUT_MSG_COUNT_MAX = 400; + + document.getElementById('output-clear').addEventListener('click', () => { + while (outputScroll.firstChild) { + outputScroll.firstChild.remove(); + } + }); + + outputRoot.style.display = 'block'; + + function print(text) { + if (arguments.length > 1) { + text = Array.prototype.slice.call(arguments).join(" "); + } + if (text.length <= 0) return; + while (outputScroll.childElementCount >= OUTPUT_MSG_COUNT_MAX) { + outputScroll.firstChild.remove(); + } + var msg = document.createElement("div"); + if (String.prototype.trim.call(text).startsWith("**ERROR**")) { + msg.style.color = "#d44"; + } else if (String.prototype.trim.call(text).startsWith("**WARNING**")) { + msg.style.color = "#ccc000"; + } else if (String.prototype.trim.call(text).startsWith("**SCRIPT ERROR**")) { + msg.style.color = "#c6d"; + } + msg.textContent = text; + var scrollToBottom = outputScroll.scrollHeight - (outputScroll.clientHeight + outputScroll.scrollTop) < 10; + outputScroll.appendChild(msg); + if (scrollToBottom) { + outputScroll.scrollTop = outputScroll.scrollHeight; + } + }; + + function printError(text) { + print('**ERROR**' + ":", text); + } + + game.setStdoutFunc(text => { + print(text); + console.log(text); + }); + + game.setStderrFunc(text => { + printError(text); + console.warn(text); + }); + } + + game.start(BASENAME + '.pck').then(() => { + setStatusMode('hidden'); + initializing = false; + }, err => { + if (DEBUG_ENABLED) + printError(err.message); + setStatusNotice(err.message); + setStatusMode('notice'); + initializing = false; + }); + })(); + //]]></script> +</body> +</html> diff --git a/misc/dist/html_fs/godotfs.js b/misc/dist/html_fs/godotfs.js deleted file mode 100644 index 676ee689fb..0000000000 --- a/misc/dist/html_fs/godotfs.js +++ /dev/null @@ -1,151 +0,0 @@ - -var Module; -if (typeof Module === 'undefined') Module = eval('(function() { try { return Module || {} } catch(e) { return {} } })()'); -if (!Module.expectedDataFileDownloads) { - Module.expectedDataFileDownloads = 0; - Module.finishedDataFileDownloads = 0; -} -Module.expectedDataFileDownloads++; -(function() { - - const PACK_FILE_NAME = '$GODOT_PACK_NAME'; - const PACK_FILE_SIZE = $GODOT_PACK_SIZE; - function fetchRemotePackage(packageName, callback, errback) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', packageName, true); - xhr.responseType = 'arraybuffer'; - xhr.onprogress = function(event) { - var url = packageName; - if (event.loaded && event.total) { - if (!xhr.addedTotal) { - xhr.addedTotal = true; - if (!Module.dataFileDownloads) Module.dataFileDownloads = {}; - Module.dataFileDownloads[url] = { - loaded: event.loaded, - total: event.total - }; - } else { - Module.dataFileDownloads[url].loaded = event.loaded; - } - var total = 0; - var loaded = 0; - var num = 0; - for (var download in Module.dataFileDownloads) { - var data = Module.dataFileDownloads[download]; - total += data.total; - loaded += data.loaded; - num++; - } - total = Math.ceil(total * Module.expectedDataFileDownloads/num); - if (Module['setStatus']) Module['setStatus']('Downloading data... (' + loaded + '/' + total + ')'); - } else if (!Module.dataFileDownloads) { - if (Module['setStatus']) Module['setStatus']('Downloading data...'); - } - }; - xhr.onload = function(event) { - var packageData = xhr.response; - callback(packageData); - }; - xhr.send(null); - }; - - function handleError(error) { - console.error('package error:', error); - }; - - var fetched = null, fetchedCallback = null; - fetchRemotePackage(PACK_FILE_NAME, function(data) { - if (fetchedCallback) { - fetchedCallback(data); - fetchedCallback = null; - } else { - fetched = data; - } - }, handleError); - - function runWithFS() { - -function assert(check, msg) { - if (!check) throw msg + new Error().stack; -} - - function DataRequest(start, end, crunched, audio) { - this.start = start; - this.end = end; - this.crunched = crunched; - this.audio = audio; - } - DataRequest.prototype = { - requests: {}, - open: function(mode, name) { - this.name = name; - this.requests[name] = this; - Module['addRunDependency']('fp ' + this.name); - }, - send: function() {}, - onload: function() { - var byteArray = this.byteArray.subarray(this.start, this.end); - - this.finish(byteArray); - - }, - finish: function(byteArray) { - var that = this; - Module['FS_createPreloadedFile'](this.name, null, byteArray, true, true, function() { - Module['removeRunDependency']('fp ' + that.name); - }, function() { - if (that.audio) { - Module['removeRunDependency']('fp ' + that.name); // workaround for chromium bug 124926 (still no audio with this, but at least we don't hang) - } else { - Module.printErr('Preloading file ' + that.name + ' failed'); - } - }, false, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change - this.requests[this.name] = null; - }, - }; - new DataRequest(0, PACK_FILE_SIZE, 0, 0).open('GET', '/' + PACK_FILE_NAME); - - var PACKAGE_PATH; - if (typeof window === 'object') { - PACKAGE_PATH = window['encodeURIComponent'](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf('/')) + '/'); - } else { - // worker - PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf('/')) + '/'); - } - var PACKAGE_NAME = PACK_FILE_NAME; - var REMOTE_PACKAGE_NAME = PACK_FILE_NAME; - var PACKAGE_UUID = 'b39761ce-0348-4959-9b16-302ed8e1592e'; - - function processPackageData(arrayBuffer) { - Module.finishedDataFileDownloads++; - assert(arrayBuffer, 'Loading data file failed.'); - var byteArray = new Uint8Array(arrayBuffer); - var curr; - - // Reuse the bytearray from the XHR as the source for file reads. - DataRequest.prototype.byteArray = byteArray; - DataRequest.prototype.requests['/' + PACK_FILE_NAME].onload(); - Module['removeRunDependency']('datafile_datapack'); - - }; - Module['addRunDependency']('datafile_datapack'); - - if (!Module.preloadResults) Module.preloadResults = {}; - - Module.preloadResults[PACKAGE_NAME] = {fromCache: false}; - if (fetched) { - processPackageData(fetched); - fetched = null; - } else { - fetchedCallback = processPackageData; - } - - } - if (Module['calledRun']) { - runWithFS(); - } else { - if (!Module['preRun']) Module['preRun'] = []; - Module["preRun"].push(runWithFS); // FS is not initialized yet, wait for it - } - -})(); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index d809109987..559e9fa455 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -248,7 +248,7 @@ void unregister_gdnative_types() { memdelete(GDNativeCallRegistry::singleton); #ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { + if (Engine::get_singleton()->is_editor_hint() && discoverer != NULL) { memdelete(discoverer); } #endif diff --git a/modules/gdscript/gd_functions.cpp b/modules/gdscript/gd_functions.cpp index f0cfdd6258..04752dc71a 100644 --- a/modules/gdscript/gd_functions.cpp +++ b/modules/gdscript/gd_functions.cpp @@ -1177,20 +1177,28 @@ void GDFunctions::call(Function p_func, const Variant **p_args, int p_arg_count, VALIDATE_ARG_COUNT(1); switch (p_args[0]->get_type()) { + case Variant::STRING: { + + String d = *p_args[0]; + r_ret = d.length(); + } break; case Variant::DICTIONARY: { + Dictionary d = *p_args[0]; r_ret = d.size(); } break; case Variant::ARRAY: { + Array d = *p_args[0]; r_ret = d.size(); } break; case Variant::POOL_BYTE_ARRAY: { + PoolVector<uint8_t> d = *p_args[0]; r_ret = d.size(); - } break; case Variant::POOL_INT_ARRAY: { + PoolVector<int> d = *p_args[0]; r_ret = d.size(); } break; @@ -1200,14 +1208,14 @@ void GDFunctions::call(Function p_func, const Variant **p_args, int p_arg_count, r_ret = d.size(); } break; case Variant::POOL_STRING_ARRAY: { + PoolVector<String> d = *p_args[0]; r_ret = d.size(); - } break; case Variant::POOL_VECTOR2_ARRAY: { + PoolVector<Vector2> d = *p_args[0]; r_ret = d.size(); - } break; case Variant::POOL_VECTOR3_ARRAY: { diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index b804863ee1..e282041745 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -20,33 +20,32 @@ for x in javascript_files: javascript_objects.append(env_javascript.Object(x)) env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""]) -env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"']) # output file name without file extension basename = "godot" + env["PROGSUFFIX"] target_dir = env.Dir("#bin") -js_file = target_dir.File(basename + ".js") -implicit_targets = [js_file] zip_dir = target_dir.Dir('.javascript_zip') -zip_files = env.InstallAs([zip_dir.File("godot.js"), zip_dir.File("godotfs.js")], [js_file, "#misc/dist/html_fs/godotfs.js"]) +zip_files = env.InstallAs(zip_dir.File('godot.html'), '#misc/dist/html/default.html') +implicit_targets = [] if env['wasm'] == 'yes': - wasm_file = target_dir.File(basename+'.wasm') - implicit_targets.append(wasm_file) - zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm_file)) + wasm = target_dir.File(basename + '.wasm') + implicit_targets.append(wasm) + zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm)) + prejs = env.File('pre_wasm.js') else: - asmjs_files = [target_dir.File(basename+'.asm.js'), target_dir.File(basename+'.html.mem')] - zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) + asmjs_files = [target_dir.File(basename + '.asm.js'), target_dir.File(basename + '.js.mem')] implicit_targets.extend(asmjs_files) + zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) + prejs = env.File('pre_asmjs.js') -# HTML file must be the first target in the list -html_file = env.Program(["#bin/godot"] + implicit_targets, javascript_objects, PROGSUFFIX=env["PROGSUFFIX"]+".html")[0] -Depends(html_file, "godot_shell.html") +js = env.Program(['#bin/godot'] + implicit_targets, javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js')[0]; +zip_files.append(InstallAs(zip_dir.File('godot.js'), js)) -# Emscripten hardcodes file names, so replace common base name with -# placeholder while leaving extension; also change `.html.mem` to just `.mem` -fixup_html = env.Substfile(html_file, SUBST_DICT=[(basename, '$$GODOT_BASE'), ('.html.mem', '.mem')], SUBSTFILESUFFIX='.fixup.html') +postjs = env.File('engine.js') +env.Depends(js, [prejs, postjs]) +env.Append(LINKFLAGS=['--pre-js', prejs.path]) +env.Append(LINKFLAGS=['--post-js', postjs.path]) -zip_files.append(InstallAs(zip_dir.File('godot.html'), fixup_html)) -Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") +Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX'] + env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 5f066f1b15..bea8f156af 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -100,6 +100,7 @@ def configure(env): ## Link flags + env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"']) env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1']) if (env['wasm'] == 'yes'): @@ -112,6 +113,7 @@ def configure(env): else: env.Append(LINKFLAGS=['-s', 'ASM_JS=1']) env.Append(LINKFLAGS=['--separate-asm']) + env.Append(LINKFLAGS=['--memory-init-file', '1']) # TODO: Move that to opus module's config if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"): diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js new file mode 100644 index 0000000000..552f5a7e02 --- /dev/null +++ b/platform/javascript/engine.js @@ -0,0 +1,366 @@ + return Module; + }, +}; + +(function() { + var engine = Engine; + + var USING_WASM = engine.USING_WASM; + var DOWNLOAD_ATTEMPTS_MAX = 4; + + var basePath = null; + var engineLoadPromise = null; + + var loadingFiles = {}; + + function getBasePath(path) { + + if (path.endsWith('/')) + path = path.slice(0, -1); + if (path.lastIndexOf('.') > path.lastIndexOf('/')) + path = path.slice(0, path.lastIndexOf('.')); + return path; + } + + function getBaseName(path) { + + path = getBasePath(path); + return path.slice(path.lastIndexOf('/') + 1); + } + + Engine = function Engine() { + + this.rtenv = null; + + var gameInitPromise = null; + var unloadAfterInit = true; + var memorySize = 268435456; + + var progressFunc = null; + var pckProgressTracker = {}; + var lastProgress = { loaded: 0, total: 0 }; + + var canvas = null; + var stdout = null; + var stderr = null; + + this.initGame = function(mainPack) { + + if (!gameInitPromise) { + + if (mainPack === undefined) { + if (basePath !== null) { + mainPack = basePath + '.pck'; + } else { + return Promise.reject(new Error("No main pack to load specified")); + } + } + if (basePath === null) + basePath = getBasePath(mainPack); + + gameInitPromise = Engine.initEngine().then( + instantiate.bind(this) + ); + var gameLoadPromise = loadPromise(mainPack, pckProgressTracker).then(function(xhr) { return xhr.response; }); + gameInitPromise = Promise.all([gameLoadPromise, gameInitPromise]).then(function(values) { + // resolve with pck + return new Uint8Array(values[0]); + }); + if (unloadAfterInit) + gameInitPromise.then(Engine.unloadEngine); + requestAnimationFrame(animateProgress); + } + return gameInitPromise; + }; + + function instantiate(initializer) { + + var rtenvOpts = { + noInitialRun: true, + thisProgram: getBaseName(basePath), + engine: this, + }; + if (typeof stdout === 'function') + rtenvOpts.print = stdout; + if (typeof stderr === 'function') + rtenvOpts.printErr = stderr; + if (typeof WebAssembly === 'object' && initializer instanceof WebAssembly.Module) { + rtenvOpts.instantiateWasm = function(imports, onSuccess) { + WebAssembly.instantiate(initializer, imports).then(function(result) { + onSuccess(result); + }); + return {}; + }; + } else if (initializer.asm && initializer.mem) { + rtenvOpts.asm = initializer.asm; + rtenvOpts.memoryInitializerRequest = initializer.mem; + rtenvOpts.TOTAL_MEMORY = memorySize; + } else { + throw new Error("Invalid initializer"); + } + + return new Promise(function(resolve, reject) { + rtenvOpts.onRuntimeInitialized = resolve; + rtenvOpts.onAbort = reject; + rtenvOpts.engine.rtenv = Engine.RuntimeEnvironment(rtenvOpts); + }); + } + + this.start = function(mainPack) { + + return this.initGame(mainPack).then(synchronousStart.bind(this)); + }; + + function synchronousStart(pckView) { + // TODO don't expect canvas when runninng as cli tool + if (canvas instanceof HTMLCanvasElement) { + this.rtenv.canvas = canvas; + } else { + var firstCanvas = document.getElementsByTagName('canvas')[0]; + if (firstCanvas instanceof HTMLCanvasElement) { + this.rtenv.canvas = firstCanvas; + } else { + throw new Error("No canvas found"); + } + } + + var actualCanvas = this.rtenv.canvas; + var context = false; + try { + context = actualCanvas.getContext('webgl2') || actualCanvas.getContext('experimental-webgl2'); + } catch (e) {} + if (!context) { + throw new Error("WebGL 2 not available"); + } + + // canvas can grab focus on click + if (actualCanvas.tabIndex < 0) { + actualCanvas.tabIndex = 0; + } + // necessary to calculate cursor coordinates correctly + actualCanvas.style.padding = 0; + actualCanvas.style.borderWidth = 0; + actualCanvas.style.borderStyle = 'none'; + // until context restoration is implemented + actualCanvas.addEventListener('webglcontextlost', function(ev) { + alert("WebGL context lost, please reload the page"); + ev.preventDefault(); + }, false); + + this.rtenv.FS.createDataFile('/', this.rtenv.thisProgram + '.pck', pckView, true, true, true); + gameInitPromise = null; + this.rtenv.callMain(); + } + + this.setProgressFunc = function(func) { + progressFunc = func; + }; + + function animateProgress() { + + var loaded = 0; + var total = 0; + var totalIsValid = true; + var progressIsFinal = true; + + [loadingFiles, pckProgressTracker].forEach(function(tracker) { + Object.keys(tracker).forEach(function(file) { + if (!tracker[file].final) + progressIsFinal = false; + if (!totalIsValid || tracker[file].total === 0) { + totalIsValid = false; + total = 0; + } else { + total += tracker[file].total; + } + loaded += tracker[file].loaded; + }); + }); + if (loaded !== lastProgress.loaded || total !== lastProgress.total) { + lastProgress.loaded = loaded; + lastProgress.total = total; + if (typeof progressFunc === 'function') + progressFunc(loaded, total); + } + if (!progressIsFinal) + requestAnimationFrame(animateProgress); + } + + this.setCanvas = function(elem) { + canvas = elem; + }; + + this.setAsmjsMemorySize = function(size) { + memorySize = size; + }; + + this.setUnloadAfterInit = function(enabled) { + + if (enabled && !unloadAfterInit && gameInitPromise) { + gameInitPromise.then(Engine.unloadEngine); + } + unloadAfterInit = enabled; + }; + + this.setStdoutFunc = function(func) { + + var print = function(text) { + if (arguments.length > 1) { + text = Array.prototype.slice.call(arguments).join(" "); + } + func(text); + }; + if (this.rtenv) + this.rtenv.print = print; + stdout = print; + }; + + this.setStderrFunc = function(func) { + + var printErr = function(text) { + if (arguments.length > 1) + text = Array.prototype.slice.call(arguments).join(" "); + func(text); + }; + if (this.rtenv) + this.rtenv.printErr = printErr; + stderr = printErr; + }; + + + }; // Engine() + + Engine.RuntimeEnvironment = engine.RuntimeEnvironment; + + Engine.initEngine = function(newBasePath) { + + if (newBasePath !== undefined) basePath = getBasePath(newBasePath); + if (engineLoadPromise === null) { + if (USING_WASM) { + if (typeof WebAssembly !== 'object') + return Promise.reject(new Error("Browser doesn't support WebAssembly")); + // TODO cache/retrieve module to/from idb + engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) { + return WebAssembly.compile(xhr.response); + }); + } else { + var asmjsPromise = loadPromise(basePath + '.asm.js').then(function(xhr) { + return asmjsModulePromise(xhr.response); + }); + var memPromise = loadPromise(basePath + '.mem'); + engineLoadPromise = Promise.all([asmjsPromise, memPromise]).then(function(values) { + return { asm: values[0], mem: values[1] }; + }); + } + engineLoadPromise = engineLoadPromise.catch(function(err) { + engineLoadPromise = null; + throw err; + }); + } + return engineLoadPromise; + }; + + function asmjsModulePromise(module) { + var elem = document.createElement('script'); + var script = new Blob([ + 'Engine.asm = (function() { var Module = {};', + module, + 'return Module.asm; })();' + ]); + var url = URL.createObjectURL(script); + elem.src = url; + return new Promise(function(resolve, reject) { + elem.addEventListener('load', function() { + URL.revokeObjectURL(url); + var asm = Engine.asm; + Engine.asm = undefined; + setTimeout(function() { + // delay to reclaim compilation memory + resolve(asm); + }, 1); + }); + elem.addEventListener('error', function() { + URL.revokeObjectURL(url); + reject("asm.js faiilure"); + }); + document.body.appendChild(elem); + }); + } + + Engine.unloadEngine = function() { + engineLoadPromise = null; + }; + + function loadPromise(file, tracker) { + if (tracker === undefined) + tracker = loadingFiles; + return new Promise(function(resolve, reject) { + loadXHR(resolve, reject, file, tracker); + }); + } + + function loadXHR(resolve, reject, file, tracker) { + + var xhr = new XMLHttpRequest; + xhr.open('GET', file); + if (!file.endsWith('.js')) { + xhr.responseType = 'arraybuffer'; + } + ['loadstart', 'progress', 'load', 'error', 'timeout', 'abort'].forEach(function(ev) { + xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); + }); + xhr.send(); + } + + function onXHREvent(resolve, reject, file, tracker, ev) { + + if (this.status >= 400) { + + if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + reject(new Error("Failed loading file '" + file + "': " + this.statusText)); + this.abort(); + return; + } else { + loadXHR(resolve, reject, file); + } + } + + switch (ev.type) { + case 'loadstart': + if (tracker[file] === undefined) { + tracker[file] = { + total: ev.total, + loaded: ev.loaded, + attempts: 0, + final: false, + }; + } + break; + + case 'progress': + tracker[file].loaded = ev.loaded; + tracker[file].total = ev.total; + break; + + case 'load': + tracker[file].final = true; + resolve(this); + break; + + case 'error': + case 'timeout': + if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + tracker[file].final = true; + reject(new Error("Failed loading file '" + file + "'")); + } else { + loadXHR(resolve, reject, file); + } + break; + + case 'abort': + tracker[file].final = true; + reject(new Error("Loading file '" + file + "' was aborted.")); + break; + } + } +})(); diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 5a161dd0ab..4a97bf4c32 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -100,8 +100,8 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re for (int i = 0; i < lines.size(); i++) { String current_line = lines[i]; - current_line = current_line.replace("$GODOT_TMEM", itos(memory_mb * 1024 * 1024)); - current_line = current_line.replace("$GODOT_BASE", p_name); + current_line = current_line.replace("$GODOT_TOTAL_MEMORY", itos(memory_mb * 1024 * 1024)); + current_line = current_line.replace("$GODOT_BASENAME", p_name); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); str_export += current_line + "\n"; @@ -114,28 +114,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re } } -void EditorExportPlatformJavaScript::_fix_fsloader_js(Vector<uint8_t> &p_js, const String &p_pack_name, uint64_t p_pack_size) { - - String str_template = String::utf8(reinterpret_cast<const char *>(p_js.ptr()), p_js.size()); - String str_export; - Vector<String> lines = str_template.split("\n"); - for (int i = 0; i < lines.size(); i++) { - if (lines[i].find("$GODOT_PACK_NAME") != -1) { - str_export += lines[i].replace("$GODOT_PACK_NAME", p_pack_name); - } else if (lines[i].find("$GODOT_PACK_SIZE") != -1) { - str_export += lines[i].replace("$GODOT_PACK_SIZE", itos(p_pack_size)); - } else { - str_export += lines[i] + "\n"; - } - } - - CharString cs = str_export.utf8(); - p_js.resize(cs.length()); - for (int i = 0; i < cs.length(); i++) { - p_js[i] = cs[i]; - } -} - void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { if (p_preset->get("texture_format/s3tc")) { @@ -286,10 +264,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug); file = p_path.get_file(); - } else if (file == "godotfs.js") { - - _fix_fsloader_js(data, pck_path.get_file(), pack_size); - file = p_path.get_file().get_basename() + "fs.js"; } else if (file == "godot.js") { file = p_path.get_file().get_basename() + ".js"; diff --git a/platform/javascript/godot_shell.html b/platform/javascript/godot_shell.html deleted file mode 100644 index ee7399a129..0000000000 --- a/platform/javascript/godot_shell.html +++ /dev/null @@ -1,347 +0,0 @@ -<!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> -<head> - <meta charset="utf-8" /> - <title></title> - <style type="text/css"> - body { - margin: 0; - border: 0 none; - padding: 0; - text-align: center; - background-color: #222226; - font-family: 'Droid Sans', Arial, sans-serif; - } - - - /* Godot Engine default theme style - * ================================ */ - - .godot { - color: #e0e0e0; - background-color: #3b3943; - background-image: linear-gradient(to bottom, #403e48, #35333c); - border: 1px solid #45434e; - box-shadow: 0 0 1px 1px #2f2d35; - } - - button.godot { - font-family: 'Droid Sans', Arial, sans-serif; /* override user agent style */ - padding: 1px 5px; - background-color: #37353f; - background-image: linear-gradient(to bottom, #413e49, #3a3842); - border: 1px solid #514f5d; - border-radius: 1px; - box-shadow: 0 0 1px 1px #2a2930; - } - - button.godot:hover { - color: #f0f0f0; - background-color: #44414e; - background-image: linear-gradient(to bottom, #494652, #423f4c); - border: 1px solid #5a5667; - box-shadow: 0 0 1px 1px #26252b; - } - - button.godot:active { - color: #fff; - background-color: #3e3b46; - background-image: linear-gradient(to bottom, #36343d, #413e49); - border: 1px solid #4f4c59; - box-shadow: 0 0 1px 1px #26252b; - } - - button.godot:disabled { - color: rgba(230, 230, 230, 0.2); - background-color: #3d3d3d; - background-image: linear-gradient(to bottom, #434343, #393939); - border: 1px solid #474747; - box-shadow: 0 0 1px 1px #2d2b33; - } - - - /* Canvas / wrapper - * ================ */ - - #container { - display: inline-block; /* scale with canvas */ - vertical-align: top; /* prevent extra height */ - position: relative; /* root for absolutely positioned overlay */ - margin: 0; - border: 0 none; - padding: 0; - background-color: #0c0c0c; - } - - #canvas { - display: block; - margin: 0 auto; - /* canvas must have border and padding set to zero to - * calculate cursor coordinates correctly */ - border: 0 none; - padding: 0; - color: white; - } - - #canvas:focus { - outline: none; - } - - - /* Status display - * ============== */ - - #status-container { - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - /* don't consume click events - make children visible explicitly */ - visibility: hidden; - } - - #status { - line-height: 1.3; - cursor: pointer; - visibility: visible; - padding: 4px 6px; - } - - - /* Debug output - * ============ */ - - #output-panel { - display: none; - max-width: 700px; - font-size: small; - margin: 6px auto 0; - padding: 0 4px 4px; - text-align: left; - line-height: 2.2; - } - - #output-header { - display: flex; - justify-content: space-between; - align-items: center; - } - - #output-container { - padding: 6px; - background-color: #2c2a32; - box-shadow: inset 0 0 1px 1px #232127; - color: #bbb; - } - - #output-scroll { - line-height: 1; - height: 12em; - overflow-y: scroll; - white-space: pre-wrap; - font-size: small; - font-family: "Lucida Console", Monaco, monospace; - } - </style> -$GODOT_HEAD_INCLUDE -</head> -<body> - <div id="container"> - <canvas id="canvas" width="640" height="480" tabindex="0" oncontextmenu="event.preventDefault();"> - HTML5 canvas appears to be unsupported in the current browser.<br /> - Please try updating or use a different browser. - </canvas> - <div id="status-container"> - <span id="status" class="godot" onclick="this.style.visibility='hidden';">Downloading page...</span> - </div> - </div> - <div id="output-panel" class="godot"> - <div id="output-header"> - Output: - <button class="godot" type="button" autocomplete="off" onclick="Presentation.clearOutput();">Clear</button> - </div> - <div id="output-container"><div id="output-scroll"></div></div> - </div> - - <!-- Scripts --> - <script type="text/javascript">//<![CDATA[ - var Presentation = (function() { - var statusElement = document.getElementById("status"); - var canvasElement = document.getElementById("canvas"); - - var presentation = { - postRun: [], - setStatusVisible: function setStatusVisible(visible) { - statusElement.style.visibility = (visible ? "visible" : "hidden"); - }, - setStatus: function setStatus(text) { - if (text.length === 0) { - // emscripten sets empty string as status after "Running..." - // per timeout, but another status may have been set by then - if (Presentation.setStatus.lastText === "Running...") - Presentation.setStatusVisible(false); - return; - } - Presentation.setStatus.lastText = text; - while (statusElement.lastChild) { - statusElement.removeChild(statusElement.lastChild); - } - var lines = text.split("\n"); - lines.forEach(function(line, index) { - statusElement.appendChild(document.createTextNode(line)); - statusElement.appendChild(document.createElement("br")); - }); - var closeNote = document.createElement("span"); - closeNote.style.fontSize = "small"; - closeNote.textContent = "click to close"; - statusElement.appendChild(closeNote); - Presentation.setStatusVisible(true); - }, - isWebGL2Available: function isWebGL2Available() { - var context; - try { - context = canvasElement.getContext("webgl2") || canvasElement.getContext("experimental-webgl2"); - } catch (e) {} - return !!context; - }, - }; - - window.onerror = function(event) { presentation.setStatus("Failure during start-up\nSee JavaScript console") }; - - if ($GODOT_DEBUG_ENABLED) { // debugging enabled - var outputRoot = document.getElementById("output-panel"); - var outputElement = document.getElementById("output-scroll"); - const maxOutputMessages = 400; - - presentation.setOutputVisible = function setOutputVisible(visible) { - outputRoot.style.display = (visible ? "block" : "none"); - }; - presentation.clearOutput = function clearOutput() { - while (outputElement.firstChild) { - outputElement.firstChild.remove(); - } - }; - - presentation.setOutputVisible(true); - - presentation.print = function print(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - if (text.length <= 0) return; - while (outputElement.childElementCount >= maxOutputMessages) { - outputElement.firstChild.remove(); - } - var msg = document.createElement("div"); - if (String.prototype.trim.call(text).startsWith("**ERROR**") - || String.prototype.trim.call(text).startsWith("**EXCEPTION**")) { - msg.style.color = "#d44"; - } else if (String.prototype.trim.call(text).startsWith("**WARNING**")) { - msg.style.color = "#ccc000"; - } else if (String.prototype.trim.call(text).startsWith("**SCRIPT ERROR**")) { - msg.style.color = "#c6d"; - } - msg.textContent = text; - var scrollToBottom = outputElement.scrollHeight - (outputElement.clientHeight + outputElement.scrollTop) < 10; - outputElement.appendChild(msg); - if (scrollToBottom) { - outputElement.scrollTop = outputElement.scrollHeight; - } - }; - - presentation.postRun.push(function() { - window.onerror = function(event) { presentation.print("**EXCEPTION**:", event) }; - }); - - } else { - presentation.postRun.push(function() { window.onerror = null; }); - } - - return presentation; - })(); - - // Emscripten interface - var Module = (function() { - const BASE_NAME = '$GODOT_BASE'; - var module = { - thisProgram: BASE_NAME, - wasmBinaryFile: BASE_NAME + '.wasm', - TOTAL_MEMORY: $GODOT_TMEM, - print: function print(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - console.log(text); - if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") { - Presentation.print(text); - } - }, - printErr: function printErr(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - console.error(text); - if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") { - Presentation.print("**ERROR**:", text) - } - }, - canvas: document.getElementById("canvas"), - setStatus: function setStatus(text) { - var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); - var now = Date.now(); - if (m) { - if (now - Date.now() < 30) // if this is a progress update, skip it if too soon - return; - text = m[1]; - } - if (typeof Presentation !== "undefined" && typeof Presentation.setStatus == "function") { - Presentation.setStatus(text); - } - } - }; - - // As a default initial behavior, pop up an alert when WebGL context is lost. To make your - // application robust, you may want to override this behavior before shipping! - // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 - module.canvas.addEventListener("webglcontextlost", function(e) { alert("WebGL context lost. Plase reload the page."); e.preventDefault(); }, false); - - if (typeof Presentation !== "undefined" && Presentation.postRun instanceof Array) { - module.postRun = Presentation.postRun; - } - - return module; - })(); - - if (!Presentation.isWebGL2Available()) { - Presentation.setStatus("WebGL 2 appears to be unsupported.\nPlease update browser and drivers."); - Presentation.preventLoading = true; - } else { - Presentation.setStatus("Downloading..."); - } - - if (Presentation.preventLoading) { - // prevent *fs.js and Emscripten's SCRIPT placeholder from loading any files - Presentation._XHR_send = XMLHttpRequest.prototype.send; - XMLHttpRequest.prototype.send = function() {}; - Presentation._Node_appendChild = Node.prototype.appendChild; - Node.prototype.appendChild = function(node) { - if (!(node instanceof HTMLScriptElement)) { - return Presentation._Node_appendChild.call(this, node); - } - } - } - //]]></script> - <script type="text/javascript" src="$GODOT_BASEfs.js"></script> -{{{ SCRIPT }}} - <script type="text/javascript"> - if (Presentation.preventLoading) { - XMLHttpRequest.prototype.send = Presentation._XHR_send; - Node.prototype.appendChild = Presentation._Node_appendChild; - } - </script> -</body> -</html> diff --git a/platform/javascript/pre_asmjs.js b/platform/javascript/pre_asmjs.js new file mode 100644 index 0000000000..3c497721b6 --- /dev/null +++ b/platform/javascript/pre_asmjs.js @@ -0,0 +1,3 @@ +var Engine = { + USING_WASM: false, + RuntimeEnvironment: function(Module) { diff --git a/platform/javascript/pre_wasm.js b/platform/javascript/pre_wasm.js new file mode 100644 index 0000000000..be4383c8c9 --- /dev/null +++ b/platform/javascript/pre_wasm.js @@ -0,0 +1,3 @@ +var Engine = { + USING_WASM: true, + RuntimeEnvironment: function(Module) { diff --git a/scene/3d/arvr_nodes.cpp b/scene/3d/arvr_nodes.cpp index caf313190b..966dd88a2c 100644 --- a/scene/3d/arvr_nodes.cpp +++ b/scene/3d/arvr_nodes.cpp @@ -31,7 +31,6 @@ #include "arvr_nodes.h" #include "core/os/input.h" #include "servers/arvr/arvr_interface.h" -#include "servers/arvr/arvr_positional_tracker.h" #include "servers/arvr_server.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -142,6 +141,7 @@ void ARVRController::_bind_methods() { ClassDB::bind_method(D_METHOD("get_joystick_axis", "axis"), &ARVRController::get_joystick_axis); ClassDB::bind_method(D_METHOD("get_is_active"), &ARVRController::get_is_active); + ClassDB::bind_method(D_METHOD("get_hand"), &ARVRController::get_hand); ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::INT, "button"))); ADD_SIGNAL(MethodInfo("button_release", PropertyInfo(Variant::INT, "button"))); @@ -204,6 +204,19 @@ bool ARVRController::get_is_active() const { return is_active; }; +ARVRPositionalTracker::TrackerHand ARVRController::get_hand() const { + // get our ARVRServer + ARVRServer *arvr_server = ARVRServer::get_singleton(); + ERR_FAIL_NULL_V(arvr_server, ARVRPositionalTracker::TRACKER_HAND_UNKNOWN); + + ARVRPositionalTracker *tracker = arvr_server->find_by_type_and_id(ARVRServer::TRACKER_CONTROLLER, controller_id); + if (tracker == NULL) { + return ARVRPositionalTracker::TRACKER_HAND_UNKNOWN; + }; + + return tracker->get_hand(); +}; + String ARVRController::get_configuration_warning() const { if (!is_visible() || !is_inside_tree()) return String(); diff --git a/scene/3d/arvr_nodes.h b/scene/3d/arvr_nodes.h index 4c14be71b5..5269ec0248 100644 --- a/scene/3d/arvr_nodes.h +++ b/scene/3d/arvr_nodes.h @@ -33,6 +33,7 @@ #include "scene/3d/camera.h" #include "scene/3d/spatial.h" +#include "servers/arvr/arvr_positional_tracker.h" /** @author Bastiaan Olij <mux213@gmail.com> @@ -84,6 +85,7 @@ public: float get_joystick_axis(int p_axis) const; bool get_is_active() const; + ARVRPositionalTracker::TrackerHand get_hand() const; String get_configuration_warning() const; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index c3d9d97c5a..a543dba9cb 100755 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -2117,7 +2117,15 @@ Node *Node::_duplicate(int p_flags) const { if (!(p_flags & DUPLICATE_SCRIPTS) && name == "script/script") continue; - node->set(name, get(name)); + Variant value = get(name); + // Duplicate dictionaries and arrays, mainly needed for __meta__ + if (value.get_type() == Variant::DICTIONARY) { + value = Dictionary(value).copy(); + } else if (value.get_type() == Variant::ARRAY) { + value = Array(value).duplicate(); + } + + node->set(name, value); } node->set_name(get_name()); @@ -2199,7 +2207,16 @@ void Node::_duplicate_and_reown(Node *p_new_parent, const Map<Node *, Node *> &p if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) continue; String name = E->get().name; - node->set(name, get(name)); + + Variant value = get(name); + // Duplicate dictionaries and arrays, mainly needed for __meta__ + if (value.get_type() == Variant::DICTIONARY) { + value = Dictionary(value).copy(); + } else if (value.get_type() == Variant::ARRAY) { + value = Array(value).duplicate(); + } + + node->set(name, value); } node->set_name(get_name()); diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index a187692bcb..abe9a00c3f 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -67,8 +67,8 @@ RID Material::get_rid() const { } void Material::_validate_property(PropertyInfo &property) const { - if (!_can_do_next_pass() && property.name=="next_pass") { - property.usage=0; + if (!_can_do_next_pass() && property.name == "next_pass") { + property.usage = 0; } } @@ -80,7 +80,7 @@ void Material::_bind_methods() { ClassDB::bind_method(D_METHOD("set_render_priority", "priority"), &Material::set_render_priority); ClassDB::bind_method(D_METHOD("get_render_priority"), &Material::get_render_priority); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "render_priority", PROPERTY_HINT_RANGE, itos(RENDER_PRIORITY_MIN) + "," + itos(RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "render_priority", PROPERTY_HINT_RANGE, itos(RENDER_PRIORITY_MIN) + "," + itos(RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "next_pass", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_next_pass", "get_next_pass"); BIND_CONSTANT(RENDER_PRIORITY_MAX); @@ -212,7 +212,7 @@ void ShaderMaterial::get_argument_options(const StringName &p_function, int p_id bool ShaderMaterial::_can_do_next_pass() const { - return shader.is_valid() && shader->get_mode()==Shader::MODE_SPATIAL; + return shader.is_valid() && shader->get_mode() == Shader::MODE_SPATIAL; } ShaderMaterial::ShaderMaterial() { diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index aa7827a61a..04efe88102 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -761,7 +761,7 @@ Array ArrayMesh::surface_get_arrays(int p_surface) const { Array ArrayMesh::surface_get_blend_shape_arrays(int p_surface) const { ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); - return Array(); + return VisualServer::get_singleton()->mesh_surface_get_blend_shape_arrays(mesh, p_surface); } int ArrayMesh::get_surface_count() const { @@ -1010,6 +1010,8 @@ void ArrayMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("surface_get_material", "surf_idx"), &ArrayMesh::surface_get_material); ClassDB::bind_method(D_METHOD("surface_set_name", "surf_idx", "name"), &ArrayMesh::surface_set_name); ClassDB::bind_method(D_METHOD("surface_get_name", "surf_idx"), &ArrayMesh::surface_get_name); + ClassDB::bind_method(D_METHOD("surface_get_arrays", "surf_idx"), &ArrayMesh::surface_get_arrays); + ClassDB::bind_method(D_METHOD("surface_get_blend_shape_arrays", "surf_idx"), &ArrayMesh::surface_get_blend_shape_arrays); ClassDB::bind_method(D_METHOD("create_trimesh_shape"), &ArrayMesh::create_trimesh_shape); ClassDB::bind_method(D_METHOD("create_convex_shape"), &ArrayMesh::create_convex_shape); ClassDB::bind_method(D_METHOD("create_outline", "margin"), &ArrayMesh::create_outline); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 53c6eb2d89..f4edb258b6 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -120,6 +120,7 @@ public: virtual int surface_get_array_len(int p_idx) const = 0; virtual int surface_get_array_index_len(int p_idx) const = 0; virtual Array surface_get_arrays(int p_surface) const = 0; + virtual Array surface_get_blend_shape_arrays(int p_surface) const = 0; virtual uint32_t surface_get_format(int p_idx) const = 0; virtual PrimitiveType surface_get_primitive_type(int p_idx) const = 0; virtual Ref<Material> surface_get_material(int p_idx) const = 0; @@ -174,7 +175,7 @@ public: void add_surface(uint32_t p_format, PrimitiveType p_primitive, const PoolVector<uint8_t> &p_array, int p_vertex_count, const PoolVector<uint8_t> &p_index_array, int p_index_count, const Rect3 &p_aabb, const Vector<PoolVector<uint8_t> > &p_blend_shapes = Vector<PoolVector<uint8_t> >(), const Vector<Rect3> &p_bone_aabbs = Vector<Rect3>()); Array surface_get_arrays(int p_surface) const; - virtual Array surface_get_blend_shape_arrays(int p_surface) const; + Array surface_get_blend_shape_arrays(int p_surface) const; void add_blend_shape(const StringName &p_name); int get_blend_shape_count() const; diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index cfc1468533..52e080bdc8 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -105,6 +105,15 @@ Array PrimitiveMesh::surface_get_arrays(int p_surface) const { return VisualServer::get_singleton()->mesh_surface_get_arrays(mesh, 0); } +Array PrimitiveMesh::surface_get_blend_shape_arrays(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, 1, Array()); + if (pending_request) { + _update(); + } + + return Array(); +} + uint32_t PrimitiveMesh::surface_get_format(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, 1, 0); if (pending_request) { @@ -119,6 +128,8 @@ Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { } Ref<Material> PrimitiveMesh::surface_get_material(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, NULL); + return material; } @@ -151,6 +162,8 @@ void PrimitiveMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_material", "material"), &PrimitiveMesh::set_material); ClassDB::bind_method(D_METHOD("get_material"), &PrimitiveMesh::get_material); + ClassDB::bind_method(D_METHOD("get_mesh_arrays"), &PrimitiveMesh::get_mesh_arrays); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "Material"), "set_material", "get_material"); } @@ -168,6 +181,10 @@ Ref<Material> PrimitiveMesh::get_material() const { return material; } +Array PrimitiveMesh::get_mesh_arrays() const { + return surface_get_arrays(0); +} + PrimitiveMesh::PrimitiveMesh() { // defaults mesh = VisualServer::get_singleton()->mesh_create(); diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index 34fb75a196..38a5695883 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -67,6 +67,7 @@ public: virtual int surface_get_array_len(int p_idx) const; virtual int surface_get_array_index_len(int p_idx) const; virtual Array surface_get_arrays(int p_surface) const; + virtual Array surface_get_blend_shape_arrays(int p_surface) const; virtual uint32_t surface_get_format(int p_idx) const; virtual Mesh::PrimitiveType surface_get_primitive_type(int p_idx) const; virtual Ref<Material> surface_get_material(int p_idx) const; @@ -78,6 +79,8 @@ public: void set_material(const Ref<Material> &p_material); Ref<Material> get_material() const; + Array get_mesh_arrays() const; + PrimitiveMesh(); ~PrimitiveMesh(); }; diff --git a/servers/arvr/arvr_positional_tracker.cpp b/servers/arvr/arvr_positional_tracker.cpp index 539bac6703..4ecd7a3898 100644 --- a/servers/arvr/arvr_positional_tracker.cpp +++ b/servers/arvr/arvr_positional_tracker.cpp @@ -31,6 +31,10 @@ #include "core/os/input.h" void ARVRPositionalTracker::_bind_methods() { + BIND_ENUM_CONSTANT(TRACKER_HAND_UNKNOWN); + BIND_ENUM_CONSTANT(TRACKER_LEFT_HAND); + BIND_ENUM_CONSTANT(TRACKER_RIGHT_HAND); + // this class is read only from GDScript, so we only have access to getters.. ClassDB::bind_method(D_METHOD("get_type"), &ARVRPositionalTracker::get_type); ClassDB::bind_method(D_METHOD("get_name"), &ARVRPositionalTracker::get_name); @@ -39,6 +43,7 @@ void ARVRPositionalTracker::_bind_methods() { ClassDB::bind_method(D_METHOD("get_orientation"), &ARVRPositionalTracker::get_orientation); ClassDB::bind_method(D_METHOD("get_tracks_position"), &ARVRPositionalTracker::get_tracks_position); ClassDB::bind_method(D_METHOD("get_position"), &ARVRPositionalTracker::get_position); + ClassDB::bind_method(D_METHOD("get_hand"), &ARVRPositionalTracker::get_hand); ClassDB::bind_method(D_METHOD("get_transform", "adjust_by_reference_frame"), &ARVRPositionalTracker::get_transform); // these functions we don't want to expose to normal users but do need to be callable from GDNative @@ -141,6 +146,14 @@ Vector3 ARVRPositionalTracker::get_rw_position() const { return rw_position; }; +ARVRPositionalTracker::TrackerHand ARVRPositionalTracker::get_hand() const { + return hand; +}; + +void ARVRPositionalTracker::set_hand(const ARVRPositionalTracker::TrackerHand p_hand) { + hand = p_hand; +}; + Transform ARVRPositionalTracker::get_transform(bool p_adjust_by_reference_frame) const { Transform new_transform; @@ -164,6 +177,7 @@ ARVRPositionalTracker::ARVRPositionalTracker() { tracker_id = 0; tracks_orientation = false; tracks_position = false; + hand = TRACKER_HAND_UNKNOWN; }; ARVRPositionalTracker::~ARVRPositionalTracker(){ diff --git a/servers/arvr/arvr_positional_tracker.h b/servers/arvr/arvr_positional_tracker.h index f91f862ba3..ff0c150f89 100644 --- a/servers/arvr/arvr_positional_tracker.h +++ b/servers/arvr/arvr_positional_tracker.h @@ -48,6 +48,13 @@ class ARVRPositionalTracker : public Object { GDCLASS(ARVRPositionalTracker, Object); _THREAD_SAFE_CLASS_ +public: + enum TrackerHand { + TRACKER_HAND_UNKNOWN, /* unknown or not applicable */ + TRACKER_LEFT_HAND, /* controller is the left hand controller */ + TRACKER_RIGHT_HAND /* controller is the right hand controller */ + }; + private: ARVRServer::TrackerType type; // type of tracker StringName name; // (unique) name of the tracker @@ -57,6 +64,7 @@ private: Basis orientation; // our orientation bool tracks_position; // do we track position? Vector3 rw_position; // our position "in the real world, so without world_scale applied" + TrackerHand hand; // if known, the hand this tracker is held in protected: static void _bind_methods(); @@ -77,6 +85,8 @@ public: Vector3 get_position() const; // get position with world_scale applied void set_rw_position(const Vector3 &p_rw_position); Vector3 get_rw_position() const; + ARVRPositionalTracker::TrackerHand get_hand() const; + void set_hand(const ARVRPositionalTracker::TrackerHand p_hand); Transform get_transform(bool p_adjust_by_reference_frame) const; @@ -84,4 +94,6 @@ public: ~ARVRPositionalTracker(); }; +VARIANT_ENUM_CAST(ARVRPositionalTracker::TrackerHand); + #endif diff --git a/servers/arvr_server.cpp b/servers/arvr_server.cpp index bac24f6438..3308e1cc07 100644 --- a/servers/arvr_server.cpp +++ b/servers/arvr_server.cpp @@ -68,8 +68,8 @@ void ARVRServer::_bind_methods() { ADD_SIGNAL(MethodInfo("interface_added", PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("interface_removed", PropertyInfo(Variant::STRING, "name"))); - ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::INT, "type"))); - ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING, "name"))); + ADD_SIGNAL(MethodInfo("tracker_added", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::INT, "type"), PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("tracker_removed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::INT, "type"), PropertyInfo(Variant::INT, "id"))); }; real_t ARVRServer::get_world_scale() const { @@ -232,7 +232,7 @@ void ARVRServer::add_tracker(ARVRPositionalTracker *p_tracker) { ERR_FAIL_NULL(p_tracker); trackers.push_back(p_tracker); - emit_signal("tracker_added", p_tracker->get_name(), p_tracker->get_type()); + emit_signal("tracker_added", p_tracker->get_name(), p_tracker->get_type(), p_tracker->get_tracker_id()); }; void ARVRServer::remove_tracker(ARVRPositionalTracker *p_tracker) { @@ -250,7 +250,7 @@ void ARVRServer::remove_tracker(ARVRPositionalTracker *p_tracker) { ERR_FAIL_COND(idx == -1); - emit_signal("tracker_removed", p_tracker->get_name()); + emit_signal("tracker_removed", p_tracker->get_name(), p_tracker->get_type(), p_tracker->get_tracker_id()); trackers.remove(idx); }; diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index 67b847d127..47a5f4c7f3 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -1420,6 +1420,29 @@ Array VisualServer::mesh_surface_get_arrays(RID p_mesh, int p_surface) const { return _get_array_from_surface(format, vertex_data, vertex_len, index_data, index_len); } +Array VisualServer::mesh_surface_get_blend_shape_arrays(RID p_mesh, int p_surface) const { + + Vector<PoolVector<uint8_t> > blend_shape_data = mesh_surface_get_blend_shapes(p_mesh, p_surface); + if (blend_shape_data.size() > 0) { + int vertex_len = mesh_surface_get_array_len(p_mesh, p_surface); + + PoolVector<uint8_t> index_data = mesh_surface_get_index_array(p_mesh, p_surface); + int index_len = mesh_surface_get_array_index_len(p_mesh, p_surface); + + uint32_t format = mesh_surface_get_format(p_mesh, p_surface); + + Array blend_shape_array; + blend_shape_array.resize(blend_shape_data.size()); + for (int i = 0; i < blend_shape_data.size(); i++) { + blend_shape_array.set(i, _get_array_from_surface(format, blend_shape_data[i], vertex_len, index_data, index_len)); + } + + return blend_shape_array; + } else { + return Array(); + } +} + void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("force_draw"), &VisualServer::draw); diff --git a/servers/visual_server.h b/servers/visual_server.h index 7494820287..72f36f6b65 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -267,6 +267,7 @@ public: virtual PoolVector<uint8_t> mesh_surface_get_index_array(RID p_mesh, int p_surface) const = 0; virtual Array mesh_surface_get_arrays(RID p_mesh, int p_surface) const; + virtual Array mesh_surface_get_blend_shape_arrays(RID p_mesh, int p_surface) const; virtual uint32_t mesh_surface_get_format(RID p_mesh, int p_surface) const = 0; virtual PrimitiveType mesh_surface_get_primitive_type(RID p_mesh, int p_surface) const = 0; |