summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/Environment.xml2
-rw-r--r--drivers/gles3/shaders/tonemap.glsl16
-rw-r--r--editor/code_editor.cpp14
-rw-r--r--editor/icons/icon_auto_key.svg57
-rw-r--r--editor/icons/icon_ruler.svg5
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp17
-rw-r--r--editor/plugins/animation_player_editor_plugin.h1
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp5
-rw-r--r--editor/plugins/asset_library_editor_plugin.h1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp134
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h8
-rw-r--r--editor/plugins/curve_editor_plugin.cpp16
-rw-r--r--modules/assimp/editor_scene_importer_assimp.cpp1595
-rw-r--r--modules/assimp/editor_scene_importer_assimp.h147
-rw-r--r--modules/assimp/import_state.h115
-rw-r--r--modules/assimp/import_utils.h448
-rw-r--r--modules/gdscript/gdscript_editor.cpp10
-rw-r--r--modules/gdscript/gdscript_functions.cpp36
-rw-r--r--modules/gdscript/gdscript_functions.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp83
-rw-r--r--modules/jsonrpc/jsonrpc.cpp18
-rw-r--r--modules/jsonrpc/jsonrpc.h10
-rw-r--r--scene/2d/tile_map.cpp1
-rw-r--r--scene/gui/gradient_edit.cpp10
-rw-r--r--scene/gui/scroll_container.cpp21
-rw-r--r--scene/gui/text_edit.cpp12
-rw-r--r--scene/main/scene_tree.cpp33
-rw-r--r--scene/main/scene_tree.h1
-rw-r--r--thirdparty/assimp/assimp/config.h7
-rw-r--r--thirdparty/assimp/code/Common/BaseImporter.cpp27
-rw-r--r--thirdparty/assimp/code/FBX/FBXConverter.cpp96
-rw-r--r--thirdparty/assimp/code/FBX/FBXConverter.h7
-rw-r--r--thirdparty/assimp/code/FBX/FBXDocument.cpp8
-rw-r--r--thirdparty/assimp/code/FBX/FBXExporter.cpp43
-rw-r--r--thirdparty/assimp/code/FBX/FBXImporter.cpp10
-rw-r--r--thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp7
-rw-r--r--thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp6
-rw-r--r--thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp125
-rw-r--r--thirdparty/assimp/code/PostProcessing/ScaleProcess.h13
-rw-r--r--thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes12
-rw-r--r--thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html1789
-rw-r--r--thirdparty/assimp/include/assimp/.editorconfig8
-rw-r--r--thirdparty/assimp/include/assimp/BaseImporter.h61
-rw-r--r--thirdparty/assimp/include/assimp/config.h.in9
-rw-r--r--thirdparty/assimp/include/assimp/irrXMLWrapper.h144
-rw-r--r--thirdparty/assimp/include/assimp/scene.h2
46 files changed, 3627 insertions, 1564 deletions
diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml
index fcbd8a2193..9a943aba51 100644
--- a/doc/classes/Environment.xml
+++ b/doc/classes/Environment.xml
@@ -306,7 +306,7 @@
Replace glow blending mode. Replaces all pixels' color by the glow value.
</constant>
<constant name="TONE_MAPPER_LINEAR" value="0" enum="ToneMapper">
- Linear tonemapper operator. Reads the linear data and performs an exposure adjustment.
+ Linear tonemapper operator. Reads the linear data and passes it on unmodified.
</constant>
<constant name="TONE_MAPPER_REINHARDT" value="1" enum="ToneMapper">
Reinhardt tonemapper operator. Performs a variation on rendered pixels' colors by this formula: [code]color = color / (1 + color)[/code].
diff --git a/drivers/gles3/shaders/tonemap.glsl b/drivers/gles3/shaders/tonemap.glsl
index 626968bc05..f1fe1742eb 100644
--- a/drivers/gles3/shaders/tonemap.glsl
+++ b/drivers/gles3/shaders/tonemap.glsl
@@ -164,7 +164,8 @@ vec3 linear_to_srgb(vec3 color) { // convert linear rgb to srgb, assumes clamped
return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
}
-vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always outputs clamped [0;1] color
+// inputs are LINEAR, If Linear tonemapping is selected no transform is performed else outputs are clamped [0, 1] color
+vec3 apply_tonemapping(vec3 color, float white) {
#ifdef USE_REINHARD_TONEMAPPER
return tonemap_reinhard(color, white);
#endif
@@ -177,7 +178,7 @@ vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always o
return tonemap_aces(color, white);
#endif
- return clamp(color, vec3(0.0f), vec3(1.0f)); // no other selected -> linear
+ return color; // no other selected -> linear: no color transform applied
}
vec3 gather_glow(sampler2D tex, vec2 uv) { // sample all selected glow levels
@@ -220,10 +221,14 @@ vec3 apply_glow(vec3 color, vec3 glow) { // apply glow using the selected blendi
#endif
#ifdef USE_GLOW_SCREEN
+ //need color clamping
+ color = clamp(color, vec3(0.0f), vec3(1.0f));
color = max((color + glow) - (color * glow), vec3(0.0));
#endif
#ifdef USE_GLOW_SOFTLIGHT
+ //need color clamping
+ color = clamp(color, vec3(0.0f), vec3(1.0));
glow = glow * vec3(0.5f) + vec3(0.5f);
color.r = (glow.r <= 0.5f) ? (color.r - (1.0f - 2.0f * glow.r) * color.r * (1.0f - color.r)) : (((glow.r > 0.5f) && (color.r <= 0.25f)) ? (color.r + (2.0f * glow.r - 1.0f) * (4.0f * color.r * (4.0f * color.r + 1.0f) * (color.r - 1.0f) + 7.0f * color.r)) : (color.r + (2.0f * glow.r - 1.0f) * (sqrt(color.r) - color.r)));
@@ -265,14 +270,16 @@ void main() {
color *= exposure;
- // Early Tonemap & SRGB Conversion
+ // Early Tonemap & SRGB Conversion; note that Linear tonemapping does not clamp to [0, 1]; some operations below expect a [0, 1] range and will clamp
color = apply_tonemapping(color, white);
#ifdef KEEP_3D_LINEAR
// leave color as is (-> don't convert to SRGB)
#else
- color = linear_to_srgb(color); // regular linear -> SRGB conversion
+ //need color clamping
+ color = clamp(color, vec3(0.0f), vec3(1.0f));
+ color = linear_to_srgb(color); // regular linear -> SRGB conversion (needs clamped values)
#endif
// Glow
@@ -282,6 +289,7 @@ void main() {
// high dynamic range -> SRGB
glow = apply_tonemapping(glow, white);
+ glow = clamp(glow, vec3(0.0f), vec3(1.0f));
glow = linear_to_srgb(glow);
color = apply_glow(color, glow);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 606b619cac..b6cd69c3cd 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -366,14 +366,12 @@ bool FindReplaceBar::search_prev() {
if (text_edit->is_selection_active())
col--; // Skip currently selected word.
- if (line == result_line && col == result_col) {
- col -= text.length();
- if (col < 0) {
- line -= 1;
- if (line < 0)
- line = text_edit->get_line_count() - 1;
- col = text_edit->get_line(line).length();
- }
+ col -= text.length();
+ if (col < 0) {
+ line -= 1;
+ if (line < 0)
+ line = text_edit->get_line_count() - 1;
+ col = text_edit->get_line(line).length();
}
return _search(flags, line, col);
diff --git a/editor/icons/icon_auto_key.svg b/editor/icons/icon_auto_key.svg
index cbafe1ac38..3d5569397f 100644
--- a/editor/icons/icon_auto_key.svg
+++ b/editor/icons/icon_auto_key.svg
@@ -1,56 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="16"
- height="16"
- version="1.1"
- viewBox="0 0 16 16"
- id="svg6"
- sodipodi:docname="icon_auto_key.svg"
- inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
- <metadata
- id="metadata12">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <defs
- id="defs10" />
- <sodipodi:namedview
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1854"
- inkscape:window-height="1016"
- id="namedview8"
- showgrid="false"
- inkscape:zoom="10.429825"
- inkscape:cx="10.199345"
- inkscape:cy="-4.0344119"
- inkscape:window-x="66"
- inkscape:window-y="27"
- inkscape:window-maximized="1"
- inkscape:current-layer="svg6" />
- <path
- style="fill:#e0e0e0;fill-opacity:1;stroke-width:0.0333107"
- d="M 3.5469681,13.426786 C 2.7965829,13.263778 2.2774312,12.503915 2.4037297,11.753472 c 0.1081234,-0.642451 0.6006808,-1.135008 1.2431317,-1.243131 0.9667125,-0.162696 1.8555225,0.726112 1.6928259,1.692826 -0.103766,0.616558 -0.5592173,1.098057 -1.1588427,1.225117 -0.2719576,0.05763 -0.3626872,0.05741 -0.6338765,-0.0014 z m 8.0861339,-0.08275 c -0.746862,-0.13829 -1.23937,-0.720718 -1.23937,-1.465649 0,-0.527377 0.244831,-0.978806 0.679757,-1.253362 0.471386,-0.297574 1.114188,-0.297574 1.585574,0 0.682727,0.430986 0.892336,1.362194 0.460575,2.046149 -0.307786,0.487563 -0.940521,0.773963 -1.486536,0.672862 z M 0.60726032,9.8305658 V 7.7161233 L 1.1770842,7.7070075 1.7469079,7.6978939 3.1889882,5.1995916 4.6310686,2.7012893 h 3.1726318 3.1726316 l 1.442755,2.4983023 1.442755,2.4983023 0.651097,0.00903 0.651096,0.00903 v 2.1145264 2.1145257 h -0.566282 -0.566281 v -0.161225 c 0,-0.234927 -0.113135,-0.639704 -0.255664,-0.914727 -0.16895,-0.326004 -0.574198,-0.731251 -0.900202,-0.9002019 -0.656732,-0.3403483 -1.428549,-0.3403483 -2.085281,0 -0.326004,0.1689519 -0.731252,0.5741989 -0.9002019,0.9002029 -0.1425297,0.275023 -0.2556639,0.6798 -0.2556639,0.914727 v 0.161225 H 7.8570969 6.0797346 L 6.0617736,11.686851 C 6.006289,10.889347 5.447548,10.170679 4.6603773,9.884336 4.4466221,9.8065798 4.3737631,9.797427 3.9716406,9.7978134 3.5871254,9.7981885 3.4905638,9.809405 3.3054265,9.8752358 2.5067319,10.159236 1.9362359,10.884501 1.8813215,11.68568 l -0.017772,0.259329 H 1.2354063 0.60726287 Z M 12.399247,7.7466889 c 0,-0.037287 -0.02623,-0.1073444 -0.0583,-0.1556843 -0.03206,-0.04834 -0.561225,-0.958444 -1.17592,-2.0224529 L 10.047407,3.6339894 7.6977565,3.6254406 C 5.4917229,3.6174174 5.3450379,3.6204563 5.2979001,3.6754094 5.1898818,3.8013046 2.9723198,7.6840061 2.9723198,7.7472381 c 0,0.067139 0.00758,0.067247 4.7134636,0.067247 h 4.7134636 z"
- id="path6243"
- inkscape:connector-curvature="0" />
-</svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="m5 3-3 5h-1v4h1.0507812a2.5 2.5 0 0 1 2.4492188-2 2.5 2.5 0 0 1 2.4453125 2h2.1054687a2.5 2.5 0 0 1 2.4492188-2 2.5 2.5 0 0 1 2.445312 2h1.054688v-4h-1l-4-5zm1 1h3l3 4h-8z" stroke-width=".033311"/><circle cx="4.5" cy="12.5" r="1.5"/><circle cx="11.5" cy="12.5" r="1.5"/></g></svg> \ No newline at end of file
diff --git a/editor/icons/icon_ruler.svg b/editor/icons/icon_ruler.svg
new file mode 100644
index 0000000000..a16eda000b
--- /dev/null
+++ b/editor/icons/icon_ruler.svg
@@ -0,0 +1,5 @@
+<svg width="16" height="16" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0 -292.77)">
+<path transform="matrix(.26458 0 0 .26458 0 292.77)" d="m1 1v7.5 6.5h14l-14-14zm3 7 4 4h-4v-4z" fill="#e0e0e0"/>
+</g>
+</svg>
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index f42716c827..173079b6de 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -477,6 +477,19 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
_animation_selected(idx);
}
+double AnimationPlayerEditor::_get_editor_step() const {
+
+ // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
+ if (track_editor->is_snap_enabled()) {
+ const String current = player->get_assigned_animation();
+ const Ref<Animation> anim = player->get_animation(current);
+ // Use more precise snapping when holding Shift
+ return Input::get_singleton()->is_key_pressed(KEY_SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
+ }
+
+ return 0.0;
+}
+
void AnimationPlayerEditor::_animation_name_edited() {
player->stop();
@@ -1017,7 +1030,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) {
float pos = CLAMP(anim->get_length() * (p_value / frame->get_max()), 0, anim->get_length());
if (track_editor->is_snap_enabled()) {
- pos = Math::stepify(pos, anim->get_step());
+ pos = Math::stepify(pos, _get_editor_step());
}
if (player->is_valid() && !p_set) {
@@ -1068,7 +1081,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag)
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
updating = true;
- frame->set_value(Math::stepify(p_pos, track_editor->is_snap_enabled() ? anim->get_step() : 0));
+ frame->set_value(Math::stepify(p_pos, _get_editor_step()));
updating = false;
_seek_value_changed(p_pos, !p_drag);
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 2ab2df68e6..4ad30675ec 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -161,6 +161,7 @@ class AnimationPlayerEditor : public VBoxContainer {
} onion;
void _select_anim_by_name(const String &p_anim);
+ double _get_editor_step() const;
void _play_pressed();
void _play_from_pressed();
void _play_bw_pressed();
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index 894e5c7298..60b5f017d2 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -586,7 +586,7 @@ void EditorAssetLibrary::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- if (is_visible()) {
+ if (is_visible() && initial_loading) {
_repository_changed(0); // Update when shown for the first time.
}
} break;
@@ -1133,6 +1133,8 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const
} break;
case REQUESTING_SEARCH: {
+ initial_loading = false;
+
// The loading text only needs to be displayed before the first page is loaded
library_loading->hide();
@@ -1328,6 +1330,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) {
requesting = REQUESTING_NONE;
templates_only = p_templates_only;
+ initial_loading = true;
VBoxContainer *library_main = memnew(VBoxContainer);
diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h
index b17a6dfe54..6a3158889e 100644
--- a/editor/plugins/asset_library_editor_plugin.h
+++ b/editor/plugins/asset_library_editor_plugin.h
@@ -206,6 +206,7 @@ class EditorAssetLibrary : public PanelContainer {
HTTPRequest *request;
bool templates_only;
+ bool initial_loading;
enum Support {
SUPPORT_OFFICIAL,
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 2daee70474..e4cd71fec0 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -2246,6 +2246,40 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
return false;
}
+bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) {
+
+ if (tool != TOOL_RULER)
+ return false;
+
+ Ref<InputEventMouseButton> b = p_event;
+ Ref<InputEventMouseMotion> m = p_event;
+
+ Point2 previous_origin = ruler_tool_origin;
+ if (!ruler_tool_active)
+ ruler_tool_origin = snap_point(viewport->get_local_mouse_position() / zoom + view_offset) * zoom;
+
+ if (b.is_valid() && b->get_button_index() == BUTTON_LEFT) {
+ if (b->is_pressed()) {
+ ruler_tool_active = true;
+ } else {
+ ruler_tool_active = false;
+ }
+
+ viewport->update();
+ return true;
+ }
+
+ bool is_snap_active = snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+
+ if (m.is_valid() && (ruler_tool_active || (is_snap_active && previous_origin != ruler_tool_origin))) {
+
+ viewport->update();
+ return true;
+ }
+
+ return false;
+}
+
bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
@@ -2323,6 +2357,8 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
//printf("Anchors\n");
} else if ((accepted = _gui_input_select(p_event))) {
//printf("Selection\n");
+ } else if ((accepted = _gui_input_ruler_tool(p_event))) {
+ //printf("Measure\n");
} else {
//printf("Not accepted\n");
}
@@ -2350,6 +2386,9 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
case TOOL_PAN:
c = CURSOR_DRAG;
break;
+ case TOOL_RULER:
+ c = CURSOR_CROSS;
+ break;
default:
break;
}
@@ -2626,6 +2665,83 @@ void CanvasItemEditor::_draw_grid() {
}
}
+void CanvasItemEditor::_draw_ruler_tool() {
+
+ if (tool != TOOL_RULER)
+ return;
+
+ bool is_snap_active = snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+
+ if (ruler_tool_active) {
+ Color ruler_primary_color = get_color("accent_color", "Editor");
+ Color ruler_secondary_color = ruler_primary_color;
+ ruler_secondary_color.a = 0.5;
+
+ Point2 begin = ruler_tool_origin - view_offset * zoom;
+ Point2 end = snap_point(viewport->get_local_mouse_position() / zoom + view_offset) * zoom - view_offset * zoom;
+ Point2 corner = Point2(begin.x, end.y);
+ Vector2 length_vector = (begin - end).abs() / zoom;
+
+ bool draw_secondary_lines = (begin.y != corner.y && end.x != corner.x);
+
+ viewport->draw_line(begin, end, ruler_primary_color, Math::round(EDSCALE * 3), true);
+ if (draw_secondary_lines) {
+ viewport->draw_line(begin, corner, ruler_secondary_color, Math::round(EDSCALE));
+ viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE));
+ }
+
+ Ref<Font> font = get_font("bold", "EditorFonts");
+ Color font_color = get_color("font_color", "Editor");
+ Color font_secondary_color = font_color;
+ font_secondary_color.a = 0.5;
+ float text_height = font->get_height();
+ const float text_width = 76;
+
+ Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2);
+ text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);
+ text_pos.y = CLAMP(text_pos.y, text_height * 1.5, viewport->get_rect().size.y - text_height * 1.5);
+ viewport->draw_string(font, text_pos, vformat("%.2f px", length_vector.length()), font_color);
+
+ if (draw_secondary_lines) {
+
+ Point2 text_pos2 = text_pos;
+ text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2);
+ viewport->draw_string(font, text_pos2, vformat("%.2f px", length_vector.y), font_secondary_color);
+
+ text_pos2 = text_pos;
+ text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y - text_height / 2) : MAX(text_pos.y + text_height * 2, end.y - text_height / 2);
+ viewport->draw_string(font, text_pos2, vformat("%.2f px", length_vector.x), font_secondary_color);
+ }
+
+ if (is_snap_active) {
+
+ text_pos = (begin + end) / 2 + Vector2(-text_width / 2, text_height / 2);
+ text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);
+ text_pos.y = CLAMP(text_pos.y, text_height * 2.5, viewport->get_rect().size.y - text_height / 2);
+
+ if (draw_secondary_lines) {
+ viewport->draw_string(font, text_pos, vformat("%.2f units", (length_vector / grid_step).length()), font_color);
+
+ Point2 text_pos2 = text_pos;
+ text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2);
+ viewport->draw_string(font, text_pos2, vformat("%d units", (int)(length_vector.y / grid_step.y)), font_secondary_color);
+
+ text_pos2 = text_pos;
+ text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y + text_height / 2) : MAX(text_pos.y + text_height * 2, end.y + text_height / 2);
+ viewport->draw_string(font, text_pos2, vformat("%d units", (int)(length_vector.x / grid_step.x)), font_secondary_color);
+ } else {
+ viewport->draw_string(font, text_pos, vformat("%d units", roundf((length_vector / grid_step).length())), font_color);
+ }
+ }
+ } else {
+
+ if (is_snap_active) {
+ Ref<Texture> position_icon = get_icon("EditorPosition", "EditorIcons");
+ viewport->draw_texture(get_icon("EditorPosition", "EditorIcons"), ruler_tool_origin - view_offset * zoom - position_icon->get_size() / 2);
+ }
+ }
+}
+
void CanvasItemEditor::_draw_control_anchors(Control *control) {
Transform2D xform = transform * control->get_global_transform_with_canvas();
RID ci = viewport->get_canvas_item();
@@ -3358,6 +3474,7 @@ void CanvasItemEditor::_draw_viewport() {
info_overlay->set_margin(MARGIN_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10);
_draw_grid();
+ _draw_ruler_tool();
_draw_selection();
_draw_axis();
if (editor->get_edited_scene()) {
@@ -3546,6 +3663,7 @@ void CanvasItemEditor::_notification(int p_what) {
snap_config_menu->set_icon(get_icon("GuiMiniTabMenu", "EditorIcons"));
skeleton_menu->set_icon(get_icon("Bone", "EditorIcons"));
pan_button->set_icon(get_icon("ToolPan", "EditorIcons"));
+ ruler_button->set_icon(get_icon("Ruler", "EditorIcons"));
pivot_button->set_icon(get_icon("EditPivot", "EditorIcons"));
select_handle = get_icon("EditorHandle", "EditorIcons");
anchor_handle = get_icon("EditorControlAnchor", "EditorIcons");
@@ -3940,13 +4058,13 @@ void CanvasItemEditor::_button_toggle_snap(bool p_status) {
void CanvasItemEditor::_button_tool_select(int p_index) {
- ToolButton *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button };
+ ToolButton *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button, ruler_button };
for (int i = 0; i < TOOL_MAX; i++) {
tb[i]->set_pressed(i == p_index);
}
- viewport->update();
tool = (Tool)p_index;
+ viewport->update();
}
void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) {
@@ -4926,6 +5044,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
panning = false;
pan_pressed = false;
+ ruler_tool_active = false;
+ ruler_tool_origin = Point2();
+
bone_last_frame = 0;
bone_list_dirty = false;
@@ -5079,6 +5200,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
pan_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_PAN));
pan_button->set_tooltip(TTR("Pan Mode"));
+ ruler_button = memnew(ToolButton);
+ hb->add_child(ruler_button);
+ ruler_button->set_toggle_mode(true);
+ ruler_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_RULER));
+ ruler_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/ruler_mode", TTR("Ruler Mode"), KEY_R));
+ ruler_button->set_tooltip(TTR("Ruler Mode"));
+
hb->add_child(memnew(VSeparator));
snap_button = memnew(ToolButton);
@@ -5171,7 +5299,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
p->set_hide_on_checkable_item_selection(false);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Show Grid"), KEY_G), SHOW_GRID);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS);
- p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers"), KEY_R), SHOW_RULERS);
+ p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers"), KEY_MASK_CMD | KEY_R), SHOW_RULERS);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_origin", TTR("Show Origin")), SHOW_ORIGIN);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_viewport", TTR("Show Viewport")), SHOW_VIEWPORT);
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index ac7d612292..4e030c63da 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -80,6 +80,7 @@ public:
TOOL_ROTATE,
TOOL_EDIT_PIVOT,
TOOL_PAN,
+ TOOL_RULER,
TOOL_MAX
};
@@ -276,6 +277,9 @@ private:
bool panning;
bool pan_pressed;
+ bool ruler_tool_active;
+ Point2 ruler_tool_origin;
+
MenuOption last_option;
struct _SelectResult {
@@ -341,6 +345,8 @@ private:
ToolButton *pivot_button;
ToolButton *pan_button;
+ ToolButton *ruler_button;
+
ToolButton *snap_button;
MenuButton *snap_config_menu;
PopupMenu *smartsnap_config_popup;
@@ -457,6 +463,7 @@ private:
void _draw_guides();
void _draw_focus();
void _draw_grid();
+ void _draw_ruler_tool();
void _draw_control_anchors(Control *control);
void _draw_control_helpers(Control *control);
void _draw_selection();
@@ -476,6 +483,7 @@ private:
bool _gui_input_resize(const Ref<InputEvent> &p_event);
bool _gui_input_rotate(const Ref<InputEvent> &p_event);
bool _gui_input_select(const Ref<InputEvent> &p_event);
+ bool _gui_input_ruler_tool(const Ref<InputEvent> &p_event);
bool _gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted);
bool _gui_input_rulers_and_guides(const Ref<InputEvent> &p_event);
bool _gui_input_hover(const Ref<InputEvent> &p_event);
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index 5d3cef4c34..c2b6031e60 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -167,10 +167,20 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
_has_undo_data = true;
}
+ const float curve_amplitude = curve.get_max_value() - curve.get_min_value();
+ // Snap to "round" coordinates when holding Ctrl.
+ // Be more precise when holding Shift as well.
+ float snap_threshold;
+ if (mm.get_control()) {
+ snap_threshold = mm.get_shift() ? 0.025 : 0.1;
+ } else {
+ snap_threshold = 0.0;
+ }
+
if (_selected_tangent == TANGENT_NONE) {
// Drag point
- Vector2 point_pos = get_world_pos(mpos);
+ Vector2 point_pos = get_world_pos(mpos).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude));
int i = curve.set_point_offset(_selected_point, point_pos.x);
// The index may change if the point is dragged across another one
@@ -188,8 +198,8 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
} else {
// Drag tangent
- Vector2 point_pos = curve.get_point_position(_selected_point);
- Vector2 control_pos = get_world_pos(mpos);
+ const Vector2 point_pos = curve.get_point_position(_selected_point);
+ const Vector2 control_pos = get_world_pos(mpos).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude));
Vector2 dir = (control_pos - point_pos).normalized();
diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp
index 05f9120a07..e5439fd132 100644
--- a/modules/assimp/editor_scene_importer_assimp.cpp
+++ b/modules/assimp/editor_scene_importer_assimp.cpp
@@ -28,24 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "assimp/DefaultLogger.hpp"
-#include "assimp/Importer.hpp"
-#include "assimp/LogStream.hpp"
-#include "assimp/Logger.hpp"
-#include "assimp/SceneCombiner.h"
-#include "assimp/cexport.h"
-#include "assimp/cimport.h"
-#include "assimp/matrix4x4.h"
-#include "assimp/pbrmaterial.h"
-#include "assimp/postprocess.h"
-#include "assimp/scene.h"
-
+#include "editor_scene_importer_assimp.h"
#include "core/bind/core_bind.h"
#include "core/io/image_loader.h"
#include "editor/editor_file_system.h"
#include "editor/import/resource_importer_scene.h"
-#include "editor_scene_importer_assimp.h"
#include "editor_settings.h"
+#include "import_utils.h"
#include "scene/3d/camera.h"
#include "scene/3d/light.h"
#include "scene/3d/mesh_instance.h"
@@ -53,7 +42,19 @@
#include "scene/main/node.h"
#include "scene/resources/material.h"
#include "scene/resources/surface_tool.h"
-#include "zutil.h"
+
+#include <assimp/SceneCombiner.h>
+#include <assimp/cexport.h>
+#include <assimp/cimport.h>
+#include <assimp/matrix4x4.h>
+#include <assimp/pbrmaterial.h>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <zutil.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <assimp/LogStream.hpp>
+#include <assimp/Logger.hpp>
#include <string>
void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const {
@@ -92,18 +93,6 @@ uint32_t EditorSceneImporterAssimp::get_import_flags() const {
return IMPORT_SCENE;
}
-AssimpStream::AssimpStream() {
- // empty
-}
-
-AssimpStream::~AssimpStream() {
- // empty
-}
-
-void AssimpStream::write(const char *message) {
- print_verbose(String("Open Asset Import: ") + String(message).strip_edges());
-}
-
void EditorSceneImporterAssimp::_bind_methods() {
}
@@ -122,35 +111,36 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f
//}
importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT);
+
//importer.SetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD, 1.0f);
int32_t post_process_Steps = aiProcess_CalcTangentSpace |
+ aiProcess_GlobalScale | // imports models and listens to their file scale for CM to M conversions
//aiProcess_FlipUVs |
- //aiProcess_FlipWindingOrder |
+ aiProcess_FlipWindingOrder | // very important for culling so that it is done in the correct order.
//aiProcess_DropNormals |
//aiProcess_GenSmoothNormals |
- aiProcess_JoinIdenticalVertices |
+ //aiProcess_JoinIdenticalVertices |
aiProcess_ImproveCacheLocality |
- aiProcess_LimitBoneWeights |
//aiProcess_RemoveRedundantMaterials | // Causes a crash
- aiProcess_SplitLargeMeshes |
+ //aiProcess_SplitLargeMeshes |
aiProcess_Triangulate |
aiProcess_GenUVCoords |
//aiProcess_FindDegenerates |
- aiProcess_SortByPType |
- aiProcess_FindInvalidData |
+ //aiProcess_SortByPType |
+ // aiProcess_FindInvalidData |
aiProcess_TransformUVCoords |
aiProcess_FindInstances |
//aiProcess_FixInfacingNormals |
- //aiProcess_ValidateDataStructure |
+ aiProcess_ValidateDataStructure |
aiProcess_OptimizeMeshes |
//aiProcess_OptimizeGraph |
//aiProcess_Debone |
- aiProcess_EmbedTextures |
- aiProcess_SplitByBoneCount |
+ // aiProcess_EmbedTextures |
+ //aiProcess_SplitByBoneCount |
0;
- const aiScene *scene = importer.ReadFile(s_path.c_str(),
- post_process_Steps);
- ERR_FAIL_COND_V_MSG(scene == NULL, NULL, String("Open Asset Import failed to open: ") + String(importer.GetErrorString()) + ".");
+ aiScene *scene = (aiScene *)importer.ReadFile(s_path.c_str(), post_process_Steps);
+ ERR_EXPLAIN(String("Open Asset Import failed to open: ") + String(importer.GetErrorString()));
+ ERR_FAIL_COND_V(scene == NULL, NULL);
return _generate_scene(p_path, scene, p_flags, p_bake_fps, max_bone_weights);
}
@@ -281,158 +271,7 @@ T EditorSceneImporterAssimp::_interpolate_track(const Vector<float> &p_times, co
ERR_FAIL_V(p_values[0]);
}
-void EditorSceneImporterAssimp::_generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<String, Transform> &bind_xforms) {
-
- Transform mesh_offset = _get_global_assimp_node_transform(p_assimp_node);
- //mesh_offset.basis = Basis();
- for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) {
- const aiMesh *mesh = state.assimp_scene->mMeshes[i];
- int owned_by = -1;
- for (uint32_t j = 0; j < mesh->mNumBones; j++) {
- const aiBone *bone = mesh->mBones[j];
- String name = _assimp_get_string(bone->mName);
-
- if (ownership.has(name)) {
- owned_by = ownership[name];
- break;
- }
- }
-
- if (owned_by == -1) { //no owned, create new unique id
- owned_by = 1;
- for (Map<String, int>::Element *E = ownership.front(); E; E = E->next()) {
- owned_by = MAX(E->get() + 1, owned_by);
- }
- }
-
- for (uint32_t j = 0; j < mesh->mNumBones; j++) {
- const aiBone *bone = mesh->mBones[j];
- String name = _assimp_get_string(bone->mName);
- ownership[name] = owned_by;
- //store the actual full path for the bone transform
- //when skeleton finds its place in the tree, it will be restored
- bind_xforms[name] = mesh_offset * _assimp_matrix_transform(bone->mOffsetMatrix);
- }
- }
-
- for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) {
- _generate_bone_groups(state, p_assimp_node->mChildren[i], ownership, bind_xforms);
- }
-}
-
-void EditorSceneImporterAssimp::_fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &bind_xforms) {
-
- String name = _assimp_get_string(p_assimp_node->mName);
- if (name == String()) {
- name = "AuxiliaryBone" + itos(holecount++);
- }
-
- Transform pose = _assimp_matrix_transform(p_assimp_node->mTransformation);
-
- if (!ownership.has(name)) {
- //not a bone, it's a hole
- Vector<SkeletonHole> holes = p_holes;
- SkeletonHole hole; //add a new one
- hole.name = name;
- hole.pose = pose;
- hole.node = p_assimp_node;
- hole.parent = p_parent_name;
- holes.push_back(hole);
-
- for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) {
- _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, holes, bind_xforms);
- }
-
- return;
- } else if (ownership[name] != p_skeleton_id) {
- //oh, it's from another skeleton? fine.. reparent all bones to this skeleton.
- int prev_owner = ownership[name];
- ERR_FAIL_COND_MSG(skeleton_map.has(prev_owner), "A previous skeleton exists for bone '" + name + "', this type of skeleton layout is unsupported.");
- for (Map<String, int>::Element *E = ownership.front(); E; E = E->next()) {
- if (E->get() == prev_owner) {
- E->get() = p_skeleton_id;
- }
- }
- }
-
- //valid bone, first fill holes if needed
- for (int i = 0; i < p_holes.size(); i++) {
-
- int bone_idx = p_skeleton->get_bone_count();
- p_skeleton->add_bone(p_holes[i].name);
- int parent_idx = p_skeleton->find_bone(p_holes[i].parent);
- if (parent_idx >= 0) {
- p_skeleton->set_bone_parent(bone_idx, parent_idx);
- }
-
- Transform pose_transform = _get_global_assimp_node_transform(p_holes[i].node);
- p_skeleton->set_bone_rest(bone_idx, pose_transform);
-
- state.bone_owners[p_holes[i].name] = skeleton_map[p_skeleton_id];
- }
-
- //finally fill bone
-
- int bone_idx = p_skeleton->get_bone_count();
- p_skeleton->add_bone(name);
- int parent_idx = p_skeleton->find_bone(p_parent_name);
- if (parent_idx >= 0) {
- p_skeleton->set_bone_parent(bone_idx, parent_idx);
- }
- //p_skeleton->set_bone_pose(bone_idx, pose);
- if (bind_xforms.has(name)) {
- //for now this is the full path to the bone in rest pose
- //when skeleton finds it's place in the tree, it will get fixed
- p_skeleton->set_bone_rest(bone_idx, bind_xforms[name]);
- }
- state.bone_owners[name] = skeleton_map[p_skeleton_id];
- //go to children
- for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) {
- _fill_node_relationships(state, p_assimp_node->mChildren[i], ownership, skeleton_map, p_skeleton_id, p_skeleton, name, holecount, Vector<SkeletonHole>(), bind_xforms);
- }
-}
-
-void EditorSceneImporterAssimp::_generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms) {
-
- //find skeletons at this level, there may be multiple root nodes for each
- Map<int, List<aiNode *> > skeletons_found;
- for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) {
- String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName);
- if (ownership.has(name)) {
- int skeleton = ownership[name];
- if (!skeletons_found.has(skeleton)) {
- skeletons_found[skeleton] = List<aiNode *>();
- }
- skeletons_found[skeleton].push_back(p_assimp_node->mChildren[i]);
- }
- }
-
- //go via the potential skeletons found and generate the actual skeleton
- for (Map<int, List<aiNode *> >::Element *E = skeletons_found.front(); E; E = E->next()) {
- ERR_CONTINUE(skeleton_map.has(E->key())); //skeleton already exists? this can't be.. skip
- Skeleton *skeleton = memnew(Skeleton);
- //this the only way to reliably use multiple meshes with one skeleton, at the cost of less precision
- skeleton->set_use_bones_in_world_transform(true);
- skeleton_map[E->key()] = state.skeletons.size();
- state.skeletons.push_back(skeleton);
- int holecount = 1;
- //fill the bones and their relationships
- for (List<aiNode *>::Element *F = E->get().front(); F; F = F->next()) {
- _fill_node_relationships(state, F->get(), ownership, skeleton_map, E->key(), skeleton, "", holecount, Vector<SkeletonHole>(), bind_xforms);
- }
- }
-
- //go to the children
- for (uint32_t i = 0; i < p_assimp_node->mNumChildren; i++) {
- String name = _assimp_get_string(p_assimp_node->mChildren[i]->mName);
- if (ownership.has(name)) {
- continue; //a bone, so don't bother with this
- }
- _generate_skeletons(state, p_assimp_node->mChildren[i], ownership, skeleton_map, bind_xforms);
- }
-}
-
-Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) {
+Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) {
ERR_FAIL_COND_V(scene == NULL, NULL);
ImportState state;
@@ -443,60 +282,37 @@ Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const
state.fbx = false;
state.animation_player = NULL;
- real_t scale_factor = 1.0f;
- {
- //handle scale
- String ext = p_path.get_file().get_extension().to_lower();
- if (ext == "fbx") {
- if (scene->mMetaData != NULL) {
- float factor = 1.0;
- scene->mMetaData->Get("UnitScaleFactor", factor);
- scale_factor = factor * 0.01f;
- }
- state.fbx = true;
- }
- }
-
- state.root->set_scale(Vector3(scale_factor, scale_factor, scale_factor));
-
//fill light map cache
for (size_t l = 0; l < scene->mNumLights; l++) {
aiLight *ai_light = scene->mLights[l];
ERR_CONTINUE(ai_light == NULL);
- state.light_cache[_assimp_get_string(ai_light->mName)] = l;
+ state.light_cache[AssimpUtils::get_assimp_string(ai_light->mName)] = l;
}
//fill camera cache
for (size_t c = 0; c < scene->mNumCameras; c++) {
aiCamera *ai_camera = scene->mCameras[c];
ERR_CONTINUE(ai_camera == NULL);
- state.camera_cache[_assimp_get_string(ai_camera->mName)] = c;
+ state.camera_cache[AssimpUtils::get_assimp_string(ai_camera->mName)] = c;
}
if (scene->mRootNode) {
- Map<String, Transform> bind_xforms; //temporary map to store bind transforms
- //guess the skeletons, since assimp does not really support them directly
- Map<String, int> ownership; //bone names to groups
- //fill this map with bone names and which group where they detected to, going mesh by mesh
- _generate_bone_groups(state, state.assimp_scene->mRootNode, ownership, bind_xforms);
- Map<int, int> skeleton_map; //maps previously created groups to actual skeletons
- //generates the skeletons when bones are found in the hierarchy, and follows them (including gaps/holes).
- _generate_skeletons(state, state.assimp_scene->mRootNode, ownership, skeleton_map, bind_xforms);
//generate nodes
for (uint32_t i = 0; i < scene->mRootNode->mNumChildren; i++) {
- _generate_node(state, scene->mRootNode->mChildren[i], state.root);
+ _generate_node(state, NULL, scene->mRootNode->mChildren[i], state.root);
}
- //assign skeletons to nodes
-
- for (Map<MeshInstance *, Skeleton *>::Element *E = state.mesh_skeletons.front(); E; E = E->next()) {
- MeshInstance *mesh = E->key();
- Skeleton *skeleton = E->get();
- NodePath skeleton_path = mesh->get_path_to(skeleton);
- mesh->set_skeleton_path(skeleton_path);
+ // finalize skeleton
+ for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
+ Skeleton *skeleton = key_value_pair->key();
+ // convert world to local for skeleton bone rests
+ skeleton->localize_rests();
}
+
+ print_verbose("generating mesh phase from skeletal mesh");
+ generate_mesh_phase_from_skeletal_mesh(state);
}
if (p_flags & IMPORT_ANIMATION && scene->mNumAnimations) {
@@ -601,12 +417,14 @@ void EditorSceneImporterAssimp::_insert_animation_track(ImportState &scene, cons
}
}
+// animation tracks are per bone
+
void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_animation_index, int p_bake_fps) {
ERR_FAIL_INDEX(p_animation_index, (int)state.assimp_scene->mNumAnimations);
const aiAnimation *anim = state.assimp_scene->mAnimations[p_animation_index];
- String name = _assimp_anim_string_to_string(anim->mName);
+ String name = AssimpUtils::get_anim_string_from_assimp(anim->mName);
if (name == String()) {
name = "Animation " + itos(p_animation_index + 1);
}
@@ -616,7 +434,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
if (state.assimp_scene->mMetaData != NULL && Math::is_equal_approx(ticks_per_second, 0.0f)) {
int32_t time_mode = 0;
state.assimp_scene->mMetaData->Get("TimeMode", time_mode);
- ticks_per_second = _get_fbx_fps(time_mode, state.assimp_scene);
+ ticks_per_second = AssimpUtils::get_fbx_fps(time_mode, state.assimp_scene);
}
//?
@@ -637,36 +455,31 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
for (size_t i = 0; i < anim->mNumChannels; i++) {
const aiNodeAnim *track = anim->mChannels[i];
- String node_name = _assimp_get_string(track->mNodeName);
- /*
- if (node_name.find(ASSIMP_FBX_KEY) != -1) {
- String p_track_type = node_name.get_slice(ASSIMP_FBX_KEY, 1);
- if (p_track_type == "_Translation" || p_track_type == "_Rotation" || p_track_type == "_Scaling") {
- continue;
- }
- }
-*/
+ String node_name = AssimpUtils::get_assimp_string(track->mNodeName);
+
if (track->mNumRotationKeys == 0 && track->mNumPositionKeys == 0 && track->mNumScalingKeys == 0) {
continue; //do not bother
}
- bool is_bone = state.bone_owners.has(node_name);
- NodePath node_path;
- Skeleton *skeleton = NULL;
+ for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
+ Skeleton *skeleton = key_value_pair->key();
- if (is_bone) {
- skeleton = state.skeletons[state.bone_owners[node_name]];
- String path = state.root->get_path_to(skeleton);
- path += ":" + node_name;
- node_path = path;
- } else {
+ bool is_bone = skeleton->find_bone(node_name) != -1;
+ //print_verbose("Bone " + node_name + " is bone? " + (is_bone ? "Yes" : "No"));
+ NodePath node_path;
- ERR_CONTINUE(!state.node_map.has(node_name));
- Node *node = state.node_map[node_name];
- node_path = state.root->get_path_to(node);
- }
+ if (is_bone) {
+ String path = state.root->get_path_to(skeleton);
+ path += ":" + node_name;
+ node_path = path;
+ } else {
+ ERR_CONTINUE(!state.node_map.has(node_name));
+ Node *node = state.node_map[node_name];
+ node_path = state.root->get_path_to(node);
+ }
- _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name);
+ _insert_animation_track(state, anim, i, p_bake_fps, animation, ticks_per_second, skeleton, node_path, node_name);
+ }
}
//blend shape tracks
@@ -675,7 +488,7 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
const aiMeshMorphAnim *anim_mesh = anim->mMorphMeshChannels[i];
- const String prop_name = _assimp_get_string(anim_mesh->mName);
+ const String prop_name = AssimpUtils::get_assimp_string(anim_mesh->mName);
const String mesh_name = prop_name.split("*")[0];
ERR_CONTINUE(prop_name.split("*").size() != 2);
@@ -715,513 +528,22 @@ void EditorSceneImporterAssimp::_import_animation(ImportState &state, int p_anim
}
}
-float EditorSceneImporterAssimp::_get_fbx_fps(int32_t time_mode, const aiScene *p_scene) {
- switch (time_mode) {
- case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack
- case AssetImportFbx::TIME_MODE_120: return 120;
- case AssetImportFbx::TIME_MODE_100: return 100;
- case AssetImportFbx::TIME_MODE_60: return 60;
- case AssetImportFbx::TIME_MODE_50: return 50;
- case AssetImportFbx::TIME_MODE_48: return 48;
- case AssetImportFbx::TIME_MODE_30: return 30;
- case AssetImportFbx::TIME_MODE_30_DROP: return 30;
- case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: return 29.9700262f;
- case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: return 29.9700262f;
- case AssetImportFbx::TIME_MODE_PAL: return 25;
- case AssetImportFbx::TIME_MODE_CINEMA: return 24;
- case AssetImportFbx::TIME_MODE_1000: return 1000;
- case AssetImportFbx::TIME_MODE_CINEMA_ND: return 23.976f;
- case AssetImportFbx::TIME_MODE_CUSTOM:
- int32_t frame_rate;
- p_scene->mMetaData->Get("FrameRate", frame_rate);
- return frame_rate;
- }
- return 0;
-}
-
-Transform EditorSceneImporterAssimp::_get_global_assimp_node_transform(const aiNode *p_current_node) {
- aiNode const *current_node = p_current_node;
- Transform xform;
- while (current_node != NULL) {
- xform = _assimp_matrix_transform(current_node->mTransformation) * xform;
- current_node = current_node->mParent;
- }
- return xform;
-}
-
-Ref<Texture> EditorSceneImporterAssimp::_load_texture(ImportState &state, String p_path) {
- Vector<String> split_path = p_path.get_basename().split("*");
- if (split_path.size() == 2) {
- size_t texture_idx = split_path[1].to_int();
- ERR_FAIL_COND_V(texture_idx >= state.assimp_scene->mNumTextures, Ref<Texture>());
- aiTexture *tex = state.assimp_scene->mTextures[texture_idx];
- String filename = _assimp_raw_string_to_string(tex->mFilename);
- filename = filename.get_file();
- print_verbose("Open Asset Import: Loading embedded texture " + filename);
- if (tex->mHeight == 0) {
- if (tex->CheckFormat("png")) {
- Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
- ERR_FAIL_COND_V(img.is_null(), Ref<Texture>());
-
- Ref<ImageTexture> t;
- t.instance();
- t->create_from_image(img);
- t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
- return t;
- } else if (tex->CheckFormat("jpg")) {
- Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
- ERR_FAIL_COND_V(img.is_null(), Ref<Texture>());
- Ref<ImageTexture> t;
- t.instance();
- t->create_from_image(img);
- t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
- return t;
- } else if (tex->CheckFormat("dds")) {
- ERR_FAIL_V_MSG(Ref<Texture>(), "Open Asset Import: Embedded dds not implemented.");
- //Ref<Image> img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
- //ERR_FAIL_COND_V(img.is_null(), Ref<Texture>());
- //Ref<ImageTexture> t;
- //t.instance();
- //t->create_from_image(img);
- //t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
- //return t;
- }
- } else {
- Ref<Image> img;
- img.instance();
- PoolByteArray arr;
- uint32_t size = tex->mWidth * tex->mHeight;
- arr.resize(size);
- memcpy(arr.write().ptr(), tex->pcData, size);
- ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Texture>());
- //ARGB8888 to RGBA8888
- for (int32_t i = 0; i < arr.size() / 4; i++) {
- arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0];
- arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1];
- arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2];
- arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3];
- }
- img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr);
- ERR_FAIL_COND_V(img.is_null(), Ref<Texture>());
-
- Ref<ImageTexture> t;
- t.instance();
- t->create_from_image(img);
- t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
- return t;
- }
- return Ref<Texture>();
- }
- Ref<Texture> p_texture = ResourceLoader::load(p_path, "Texture");
- return p_texture;
-}
-
-Ref<Material> EditorSceneImporterAssimp::_generate_material_from_index(ImportState &state, int p_index, bool p_double_sided) {
-
- ERR_FAIL_INDEX_V(p_index, (int)state.assimp_scene->mNumMaterials, Ref<Material>());
-
- aiMaterial *ai_material = state.assimp_scene->mMaterials[p_index];
- Ref<SpatialMaterial> mat;
- mat.instance();
-
- int32_t mat_two_sided = 0;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) {
- if (mat_two_sided > 0) {
- mat->set_cull_mode(SpatialMaterial::CULL_DISABLED);
- }
- }
-
- //const String mesh_name = _assimp_get_string(ai_mesh->mName);
- aiString mat_name;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) {
- mat->set_name(_assimp_get_string(mat_name));
- }
-
- aiTextureType tex_normal = aiTextureType_NORMALS;
- {
- aiString ai_filename = aiString();
- String filename = "";
- aiTextureMapMode map_mode[2];
-
- if (AI_SUCCESS == ai_material->GetTexture(tex_normal, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) {
- filename = _assimp_raw_string_to_string(ai_filename);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
-
- if (texture.is_valid()) {
- _set_texture_mapping_mode(map_mode, texture);
- mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
- mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
- }
- }
- }
- }
-
- {
- aiString ai_filename = aiString();
- String filename = "";
-
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, ai_filename)) {
- filename = _assimp_raw_string_to_string(ai_filename);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
- mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
- }
- }
- }
- }
-
- aiTextureType tex_emissive = aiTextureType_EMISSIVE;
-
- if (ai_material->GetTextureCount(tex_emissive) > 0) {
-
- aiString ai_filename = aiString();
- String filename = "";
- aiTextureMapMode map_mode[2];
-
- if (AI_SUCCESS == ai_material->GetTexture(tex_emissive, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) {
- filename = _assimp_raw_string_to_string(ai_filename);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- _set_texture_mapping_mode(map_mode, texture);
- mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true);
- mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, texture);
- }
- }
- }
- }
-
- aiTextureType tex_albedo = aiTextureType_DIFFUSE;
- if (ai_material->GetTextureCount(tex_albedo) > 0) {
-
- aiString ai_filename = aiString();
- String filename = "";
- aiTextureMapMode map_mode[2];
- if (AI_SUCCESS == ai_material->GetTexture(tex_albedo, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) {
- filename = _assimp_raw_string_to_string(ai_filename);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- if (texture->get_data()->detect_alpha() != Image::ALPHA_NONE) {
- _set_texture_mapping_mode(map_mode, texture);
- mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- }
- mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
- }
- }
- }
- } else {
- aiColor4D clr_diffuse;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) {
- if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) {
- mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- }
- mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a));
- }
- }
-
- aiString tex_gltf_base_color_path = aiString();
- aiTextureMapMode map_mode[2];
- if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &tex_gltf_base_color_path, NULL, NULL, NULL, NULL, map_mode)) {
- String filename = _assimp_raw_string_to_string(tex_gltf_base_color_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- _find_texture_path(state.path, path, found);
- if (texture != NULL) {
- if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
- _set_texture_mapping_mode(map_mode, texture);
- mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- }
- mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
- }
- }
- } else {
- aiColor4D pbr_base_color;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, pbr_base_color)) {
- if (Math::is_equal_approx(pbr_base_color.a, 1.0f) == false) {
- mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- }
- mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a));
- }
- }
- {
- aiString tex_fbx_pbs_base_color_path = aiString();
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- _find_texture_path(state.path, path, found);
- if (texture != NULL) {
- if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
- mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- }
- mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
- }
- }
- } else {
- aiColor4D pbr_base_color;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR, pbr_base_color)) {
- mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a));
- }
- }
-
- aiUVTransform pbr_base_color_uv_xform;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM, pbr_base_color_uv_xform)) {
- mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f));
- mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f));
- }
- }
-
- {
- aiString tex_fbx_pbs_normal_path = aiString();
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE, tex_fbx_pbs_normal_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_normal_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- _find_texture_path(state.path, path, found);
- if (texture != NULL) {
- mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
- mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
- }
- }
- }
- }
-
- if (p_double_sided) {
- mat->set_cull_mode(SpatialMaterial::CULL_DISABLED);
- }
-
- {
- aiString tex_fbx_stingray_normal_path = aiString();
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE, tex_fbx_stingray_normal_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_stingray_normal_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- _find_texture_path(state.path, path, found);
- if (texture != NULL) {
- mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
- mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
- }
- }
- }
- }
-
- {
- aiString tex_fbx_pbs_base_color_path = aiString();
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_base_color_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- _find_texture_path(state.path, path, found);
- if (texture != NULL) {
- if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
- mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- }
- mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
- }
- }
- } else {
- aiColor4D pbr_base_color;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR, pbr_base_color)) {
- mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a));
- }
- }
-
- aiUVTransform pbr_base_color_uv_xform;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM, pbr_base_color_uv_xform)) {
- mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f));
- mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f));
- }
- }
-
- {
- aiString tex_fbx_pbs_emissive_path = aiString();
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE, tex_fbx_pbs_emissive_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_emissive_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- _find_texture_path(state.path, path, found);
- if (texture != NULL) {
- if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
- mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
- mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
- }
- mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
- }
- }
- } else {
- aiColor4D pbr_emmissive_color;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR, pbr_emmissive_color)) {
- mat->set_emission(Color(pbr_emmissive_color.r, pbr_emmissive_color.g, pbr_emmissive_color.b, pbr_emmissive_color.a));
- }
- }
-
- real_t pbr_emission_intensity;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR, pbr_emission_intensity)) {
- mat->set_emission_energy(pbr_emission_intensity);
- }
- }
-
- aiString tex_gltf_pbr_metallicroughness_path;
- if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &tex_gltf_pbr_metallicroughness_path)) {
- String filename = _assimp_raw_string_to_string(tex_gltf_pbr_metallicroughness_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture);
- mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE);
- mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture);
- mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN);
- }
- }
- } else {
- float pbr_roughness = 0.0f;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, pbr_roughness)) {
- mat->set_roughness(pbr_roughness);
- }
- float pbr_metallic = 0.0f;
-
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, pbr_metallic)) {
- mat->set_metallic(pbr_metallic);
- }
- }
- {
- aiString tex_fbx_pbs_metallic_path;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE, tex_fbx_pbs_metallic_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture);
- mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
- }
- }
- } else {
- float pbr_metallic = 0.0f;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR, pbr_metallic)) {
- mat->set_metallic(pbr_metallic);
- }
- }
-
- aiString tex_fbx_pbs_rough_path;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture);
- mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
- }
- }
- } else {
- float pbr_roughness = 0.04f;
-
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR, pbr_roughness)) {
- mat->set_roughness(pbr_roughness);
- }
- }
- }
-
- {
- aiString tex_fbx_pbs_metallic_path;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE, tex_fbx_pbs_metallic_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_metallic_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture);
- mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
- }
- }
- } else {
- float pbr_metallic = 0.0f;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_FACTOR, pbr_metallic)) {
- mat->set_metallic(pbr_metallic);
- }
- }
-
- aiString tex_fbx_pbs_rough_path;
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) {
- String filename = _assimp_raw_string_to_string(tex_fbx_pbs_rough_path);
- String path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
- bool found = false;
- _find_texture_path(state.path, path, found);
- if (found) {
- Ref<Texture> texture = _load_texture(state, path);
- if (texture != NULL) {
- mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture);
- mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
- }
- }
- } else {
- float pbr_roughness = 0.04f;
-
- if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR, pbr_roughness)) {
- mat->set_roughness(pbr_roughness);
- }
- }
- }
-
- return mat;
-}
-
-Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, Skeleton *p_skeleton, bool p_double_sided_material) {
+//
+// Mesh Generation from indicies ? why do we need so much mesh code
+// [debt needs looked into]
+Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(
+ ImportState &state,
+ const Vector<int> &p_surface_indices,
+ const aiNode *assimp_node,
+ Skeleton *p_skeleton) {
Ref<ArrayMesh> mesh;
mesh.instance();
bool has_uvs = false;
+ //
+ // Process Vertex Weights
+ //
for (int i = 0; i < p_surface_indices.size(); i++) {
const unsigned int mesh_idx = p_surface_indices[i];
const aiMesh *ai_mesh = state.assimp_scene->mMeshes[mesh_idx];
@@ -1231,7 +553,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
if (p_skeleton) {
for (size_t b = 0; b < ai_mesh->mNumBones; b++) {
aiBone *bone = ai_mesh->mBones[b];
- String bone_name = _assimp_get_string(bone->mName);
+ String bone_name = AssimpUtils::get_assimp_string(bone->mName);
int bone_index = p_skeleton->find_bone(bone_name);
ERR_CONTINUE(bone_index == -1); //bone refers to an unexisting index, wtf.
@@ -1244,7 +566,6 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
uint32_t vertex_index = ai_weights.mVertexId;
bi.bone = bone_index;
bi.weight = ai_weights.mWeight;
- ;
if (!vertex_weights.has(vertex_index)) {
vertex_weights[vertex_index] = Vector<BoneInfo>();
@@ -1255,23 +576,34 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
}
}
+ //
+ // Create mesh from data from assimp
+ //
+
Ref<SurfaceTool> st;
st.instance();
st->begin(Mesh::PRIMITIVE_TRIANGLES);
for (size_t j = 0; j < ai_mesh->mNumVertices; j++) {
+
+ // Get the texture coordinates if they exist
if (ai_mesh->HasTextureCoords(0)) {
has_uvs = true;
st->add_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y));
}
+
if (ai_mesh->HasTextureCoords(1)) {
has_uvs = true;
st->add_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y));
}
+
+ // Assign vertex colors
if (ai_mesh->HasVertexColors(0)) {
Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, ai_mesh->mColors[0]->a);
st->add_color(color);
}
+
+ // Work out normal calculations? - this needs work it doesn't work properly on huestos
if (ai_mesh->mNormals != NULL) {
const aiVector3D normals = ai_mesh->mNormals[j];
const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z);
@@ -1286,6 +618,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
}
}
+ // We have vertex weights right?
if (vertex_weights.has(j)) {
Vector<BoneInfo> bone_info = vertex_weights[j];
@@ -1293,6 +626,8 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
bones.resize(bone_info.size());
Vector<float> weights;
weights.resize(bone_info.size());
+
+ // todo? do we really need to loop over all bones? - assimp may have helper to find all influences on this vertex.
for (int k = 0; k < bone_info.size(); k++) {
bones.write[k] = bone_info[k].bone;
weights.write[k] = bone_info[k].weight;
@@ -1302,30 +637,152 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
st->add_weights(weights);
}
+ // Assign vertex
const aiVector3D pos = ai_mesh->mVertices[j];
+
+ // note we must include node offset transform as this is relative to world space not local space.
Vector3 godot_pos = Vector3(pos.x, pos.y, pos.z);
st->add_vertex(godot_pos);
}
+ // fire replacement for face handling
for (size_t j = 0; j < ai_mesh->mNumFaces; j++) {
const aiFace face = ai_mesh->mFaces[j];
- ERR_CONTINUE(face.mNumIndices != 3);
- Vector<size_t> order;
- order.push_back(2);
- order.push_back(1);
- order.push_back(0);
- for (int32_t k = 0; k < order.size(); k++) {
- st->add_index(face.mIndices[order[k]]);
+ for (unsigned int k = 0; k < face.mNumIndices; k++) {
+ st->add_index(face.mIndices[k]);
}
}
+
if (ai_mesh->HasTangentsAndBitangents() == false && has_uvs) {
st->generate_tangents();
}
- Ref<Material> material;
+ aiMaterial *ai_material = state.assimp_scene->mMaterials[ai_mesh->mMaterialIndex];
+ Ref<SpatialMaterial> mat;
+ mat.instance();
+
+ int32_t mat_two_sided = 0;
+ if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) {
+ if (mat_two_sided > 0) {
+ mat->set_cull_mode(SpatialMaterial::CULL_DISABLED);
+ }
+ }
+
+ const String mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mName);
+ aiString mat_name;
+ if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) {
+ mat->set_name(AssimpUtils::get_assimp_string(mat_name));
+ }
+
+ // Culling handling for meshes
+
+ // cull all back faces
+ mat->set_cull_mode(SpatialMaterial::CULL_BACK);
+
+ // Now process materials
+ aiTextureType tex_diffuse = aiTextureType_DIFFUSE;
+ {
+ String filename, path;
+ AssimpImageData image_data;
+
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_diffuse, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+
+ // anything transparent must be culled
+ if (image_data.raw_image->detect_alpha() != Image::ALPHA_NONE) {
+ mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+ mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+ mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode
+ }
+
+ mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, image_data.texture);
+ }
+
+ aiColor4D clr_diffuse;
+ if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) {
+ if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) {
+ mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+ mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+ mat->set_cull_mode(SpatialMaterial::CULL_DISABLED); // since you can see both sides in transparent mode
+ }
+ mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a));
+ }
+ }
+
+ aiTextureType tex_normal = aiTextureType_NORMALS;
+ {
+ String filename, path;
+ Ref<ImageTexture> texture;
+ AssimpImageData image_data;
+
+ // Process texture normal map
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_normal, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+ mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture);
+ } else {
+ aiString texture_path;
+ if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, AI_PROPERTIES, texture_path)) {
+ if (AssimpUtils::CreateAssimpTexture(state, texture_path, filename, path, image_data)) {
+ mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+ mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, image_data.texture);
+ }
+ }
+ }
+ }
+
+ aiTextureType tex_emissive = aiTextureType_EMISSIVE;
+ {
+ String filename = "";
+ String path = "";
+ Ref<Image> texture;
+ AssimpImageData image_data;
+
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_emissive, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true);
+ mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, image_data.texture);
+ } else {
+ // Process emission textures
+ aiString texture_emissive_path;
+ if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE, AI_PROPERTIES, texture_emissive_path)) {
+ if (AssimpUtils::CreateAssimpTexture(state, texture_emissive_path, filename, path, image_data)) {
+ mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true);
+ mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, image_data.texture);
+ }
+ } else {
+ float pbr_emission = 0.0f;
+ if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_EMISSIVE_FACTOR, AI_NULL, pbr_emission)) {
+ mat->set_emission(Color(pbr_emission, pbr_emission, pbr_emission, 1.0f));
+ }
+ }
+ }
+ }
+
+ aiTextureType tex_specular = aiTextureType_SPECULAR;
+ {
+ String filename, path;
+ Ref<ImageTexture> texture;
+ AssimpImageData image_data;
+
+ // Process texture normal map
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_specular, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, image_data.texture);
+ }
+ }
- if (!state.material_cache.has(ai_mesh->mMaterialIndex)) {
- material = _generate_material_from_index(state, ai_mesh->mMaterialIndex, p_double_sided_material);
+ aiTextureType tex_roughness = aiTextureType_SHININESS;
+ {
+ String filename, path;
+ Ref<ImageTexture> texture;
+ AssimpImageData image_data;
+
+ // Process texture normal map
+ if (AssimpUtils::GetAssimpTexture(state, ai_material, tex_roughness, filename, path, image_data)) {
+ AssimpUtils::set_texture_mapping_mode(image_data.map_mode, image_data.texture);
+ mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, image_data.texture);
+ }
}
Array array_mesh = st->commit_to_arrays();
@@ -1335,16 +792,13 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
Map<uint32_t, String> morph_mesh_idx_names;
for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) {
- if (i == 0) {
- //only do this the first time
- String ai_anim_mesh_name = _assimp_get_string(ai_mesh->mAnimMeshes[j]->mName);
- mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
- if (ai_anim_mesh_name.empty()) {
- ai_anim_mesh_name = String("morph_") + itos(j);
- }
- mesh->add_blend_shape(ai_anim_mesh_name);
+ String ai_anim_mesh_name = AssimpUtils::get_assimp_string(ai_mesh->mAnimMeshes[j]->mName);
+ mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
+ if (ai_anim_mesh_name.empty()) {
+ ai_anim_mesh_name = String("morph_") + itos(j);
}
-
+ mesh->add_blend_shape(ai_anim_mesh_name);
+ morph_mesh_idx_names.insert(j, ai_anim_mesh_name);
Array array_copy;
array_copy.resize(VisualServer::ARRAY_MAX);
@@ -1363,12 +817,11 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
vertices.write()[l] = position;
}
PoolVector3Array new_vertices = array_copy[VisualServer::ARRAY_VERTEX].duplicate(true);
-
- for (int32_t l = 0; l < vertices.size(); l++) {
+ ERR_CONTINUE(vertices.size() != new_vertices.size());
+ for (int32_t l = 0; l < new_vertices.size(); l++) {
PoolVector3Array::Write w = new_vertices.write();
w[l] = vertices[l];
}
- ERR_CONTINUE(vertices.size() != new_vertices.size());
array_copy[VisualServer::ARRAY_VERTEX] = new_vertices;
}
@@ -1382,7 +835,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
colors.write()[l] = color;
}
PoolColorArray new_colors = array_copy[VisualServer::ARRAY_COLOR].duplicate(true);
-
+ ERR_CONTINUE(colors.size() != new_colors.size());
for (int32_t l = 0; l < colors.size(); l++) {
PoolColorArray::Write w = new_colors.write();
w[l] = colors[l];
@@ -1394,12 +847,12 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
PoolVector3Array normals;
normals.resize(num_vertices);
for (size_t l = 0; l < num_vertices; l++) {
- const aiVector3D ai_normal = ai_mesh->mAnimMeshes[i]->mNormals[l];
+ const aiVector3D ai_normal = ai_mesh->mAnimMeshes[j]->mNormals[l];
Vector3 normal = Vector3(ai_normal.x, ai_normal.y, ai_normal.z);
normals.write()[l] = normal;
}
PoolVector3Array new_normals = array_copy[VisualServer::ARRAY_NORMAL].duplicate(true);
-
+ ERR_CONTINUE(normals.size() != new_normals.size());
for (int l = 0; l < normals.size(); l++) {
PoolVector3Array::Write w = new_normals.write();
w[l] = normals[l];
@@ -1412,7 +865,7 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
tangents.resize(num_vertices);
PoolColorArray::Write w = tangents.write();
for (size_t l = 0; l < num_vertices; l++) {
- _calc_tangent_from_mesh(ai_mesh, j, l, l, w);
+ AssimpUtils::calc_tangent_from_mesh(ai_mesh, j, l, l, w);
}
PoolRealArray new_tangents = array_copy[VisualServer::ARRAY_TANGENT].duplicate(true);
ERR_CONTINUE(new_tangents.size() != tangents.size() * 4);
@@ -1422,340 +875,388 @@ Ref<Mesh> EditorSceneImporterAssimp::_generate_mesh_from_surface_indices(ImportS
new_tangents.write()[l + 2] = tangents[l].b;
new_tangents.write()[l + 3] = tangents[l].a;
}
-
array_copy[VisualServer::ARRAY_TANGENT] = new_tangents;
}
morphs[j] = array_copy;
}
-
mesh->add_surface_from_arrays(primitive, array_mesh, morphs);
- mesh->surface_set_material(i, material);
- mesh->surface_set_name(i, _assimp_get_string(ai_mesh->mName));
+ mesh->surface_set_material(i, mat);
+ mesh->surface_set_name(i, AssimpUtils::get_assimp_string(ai_mesh->mName));
}
return mesh;
}
-void EditorSceneImporterAssimp::_generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent) {
+/* to be moved into assimp */
+aiBone *get_bone_by_name(const aiScene *scene, aiString bone_name) {
+ for (unsigned int mesh_id = 0; mesh_id < scene->mNumMeshes; ++mesh_id) {
+ aiMesh *mesh = scene->mMeshes[mesh_id];
- Spatial *new_node = NULL;
- String node_name = _assimp_get_string(p_assimp_node->mName);
- Transform node_transform = _assimp_matrix_transform(p_assimp_node->mTransformation);
-
- if (p_assimp_node->mNumMeshes > 0) {
- /* MESH NODE */
- Ref<Mesh> mesh;
- Skeleton *skeleton = NULL;
- {
+ // iterate over all the bones on the mesh for this node only!
+ for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; boneIndex++) {
- //see if we have mesh cache for this.
- Vector<int> surface_indices;
- for (uint32_t i = 0; i < p_assimp_node->mNumMeshes; i++) {
- int mesh_index = p_assimp_node->mMeshes[i];
- surface_indices.push_back(mesh_index);
-
- //take the chance and attempt to find the skeleton from the bones
- if (!skeleton) {
- aiMesh *ai_mesh = state.assimp_scene->mMeshes[p_assimp_node->mMeshes[i]];
- for (uint32_t j = 0; j < ai_mesh->mNumBones; j++) {
- aiBone *bone = ai_mesh->mBones[j];
- String bone_name = _assimp_get_string(bone->mName);
- if (state.bone_owners.has(bone_name)) {
- skeleton = state.skeletons[state.bone_owners[bone_name]];
- break;
- }
- }
- }
- }
- surface_indices.sort();
- String mesh_key;
- for (int i = 0; i < surface_indices.size(); i++) {
- if (i > 0) {
- mesh_key += ":";
- }
- mesh_key += itos(surface_indices[i]);
+ aiBone *bone = mesh->mBones[boneIndex];
+ if (bone->mName == bone_name) {
+ printf("matched bone by name: %s\n", bone->mName.C_Str());
+ return bone;
}
+ }
+ }
- if (!state.mesh_cache.has(mesh_key)) {
- //adding cache
- aiString cull_mode; //cull is on mesh, which is kind of stupid tbh
- bool double_sided_material = false;
- if (p_assimp_node->mMetaData) {
- p_assimp_node->mMetaData->Get("Culling", cull_mode);
- }
- if (cull_mode.length != 0 && cull_mode == aiString("CullingOff")) {
- double_sided_material = true;
- }
+ return NULL;
+}
- mesh = _generate_mesh_from_surface_indices(state, surface_indices, skeleton, double_sided_material);
- state.mesh_cache[mesh_key] = mesh;
+/**
+ * Create a new mesh for the node supplied
+ */
+void EditorSceneImporterAssimp::create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform) {
+ /* MESH NODE */
+ Ref<Mesh> mesh;
+ Skeleton *skeleton = NULL;
+ // see if we have mesh cache for this.
+ Vector<int> surface_indices;
+ for (uint32_t i = 0; i < assimp_node->mNumMeshes; i++) {
+ int mesh_index = assimp_node->mMeshes[i];
+ aiMesh *ai_mesh = state.assimp_scene->mMeshes[assimp_node->mMeshes[i]];
+
+ // Map<aiBone*, Skeleton*> // this is what we need
+ if (ai_mesh->mNumBones > 0) {
+ // we only need the first bone to retrieve the skeleton
+ const aiBone *first = ai_mesh->mBones[0];
+
+ ERR_FAIL_COND(first == NULL);
+
+ Map<const aiBone *, Skeleton *>::Element *match = state.bone_to_skeleton_lookup.find(first);
+ if (match != NULL) {
+ skeleton = match->value();
+
+ if (skeleton == NULL) {
+ print_error("failed to find bone skeleton for bone: " + AssimpUtils::get_assimp_string(first->mName));
+ } else {
+ print_verbose("successfully found skeleton for first bone on mesh, can properly handle animations now!");
+ }
+ // I really need the skeleton and bone to be known as this is something flaky in model exporters.
+ ERR_FAIL_COND(skeleton == NULL); // should not happen if bone was successfully created in previous step.
}
-
- mesh = state.mesh_cache[mesh_key];
}
+ surface_indices.push_back(mesh_index);
+ }
- MeshInstance *mesh_node = memnew(MeshInstance);
- if (skeleton) {
- state.mesh_skeletons[mesh_node] = skeleton;
+ surface_indices.sort();
+ String mesh_key;
+ for (int i = 0; i < surface_indices.size(); i++) {
+ if (i > 0) {
+ mesh_key += ":";
}
- mesh_node->set_mesh(mesh);
- new_node = mesh_node;
-
- } else if (state.light_cache.has(node_name)) {
-
- Light *light = NULL;
- aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[node_name]];
- ERR_FAIL_COND(!ai_light);
+ mesh_key += itos(surface_indices[i]);
+ }
- if (ai_light->mType == aiLightSource_DIRECTIONAL) {
- light = memnew(DirectionalLight);
- Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
- dir.normalize();
- Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
- Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z);
- up.normalize();
+ if (!state.mesh_cache.has(mesh_key)) {
+ mesh = _generate_mesh_from_surface_indices(state, surface_indices, assimp_node, skeleton);
+ state.mesh_cache[mesh_key] = mesh;
+ }
- Transform light_transform;
- light_transform.set_look_at(pos, pos + dir, up);
+ //Transform transform = recursive_state.node_transform;
- node_transform *= light_transform;
+ // we must unfortunately overwrite mesh and skeleton transform with armature data
+ if (skeleton != NULL) {
+ print_verbose("Applying mesh and skeleton to armature");
+ // required for blender, maya etc
+ Map<Skeleton *, const Spatial *>::Element *match = state.armature_skeletons.find(skeleton);
+ node_transform = match->value()->get_transform();
+ }
- } else if (ai_light->mType == aiLightSource_POINT) {
- light = memnew(OmniLight);
- Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
- Transform xform;
- xform.origin = pos;
+ MeshInstance *mesh_node = memnew(MeshInstance);
+ mesh = state.mesh_cache[mesh_key];
+ mesh_node->set_mesh(mesh);
- node_transform *= xform;
+ attach_new_node(state,
+ mesh_node,
+ assimp_node,
+ parent_node,
+ node_name,
+ node_transform);
- light->set_transform(xform);
+ // set this once and for all
+ if (skeleton != NULL) {
+ // root must be informed of its new child
+ parent_node->add_child(skeleton);
- //light->set_param(Light::PARAM_ATTENUATION, 1);
- } else if (ai_light->mType == aiLightSource_SPOT) {
- light = memnew(SpotLight);
+ // owner must be set after adding to tree
+ skeleton->set_owner(state.root);
- Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
- dir.normalize();
- Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
- Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z);
- up.normalize();
+ skeleton->set_transform(node_transform);
- Transform light_transform;
- light_transform.set_look_at(pos, pos + dir, up);
- node_transform *= light_transform;
+ // must be done after added to tree
+ mesh_node->set_skeleton_path(mesh_node->get_path_to(skeleton));
+ }
+}
- //light->set_param(Light::PARAM_ATTENUATION, 0.0f);
- }
- ERR_FAIL_COND(light == NULL);
- light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b));
- new_node = light;
- } else if (state.camera_cache.has(node_name)) {
+/** generate_mesh_phase_from_skeletal_mesh
+ * This must be executed after generate_nodes because the skeleton doesn't exist until that has completed the first pass
+ */
+void EditorSceneImporterAssimp::generate_mesh_phase_from_skeletal_mesh(ImportState &state) {
+ // prevent more than one skeleton existing per mesh
+ // * multiple root bones have this
+ // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node
+ for (Map<const aiNode *, const Node *>::Element *key_value_pair = state.assimp_node_map.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
+ const aiNode *assimp_node = key_value_pair->key();
+ Node *current_node = (Node *)key_value_pair->value();
+ Node *parent_node = current_node->get_parent();
- aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[node_name]];
- ERR_FAIL_COND(!ai_camera);
+ ERR_CONTINUE(assimp_node == NULL);
+ ERR_CONTINUE(parent_node == NULL);
- Camera *camera = memnew(Camera);
+ String node_name = AssimpUtils::get_assimp_string(assimp_node->mName);
+ Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation);
- float near = ai_camera->mClipPlaneNear;
- if (Math::is_equal_approx(near, 0.0f)) {
- near = 0.1f;
+ if (assimp_node->mNumMeshes > 0) {
+ create_mesh(state, assimp_node, node_name, current_node, parent_node, node_transform);
}
- camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar);
+ }
+}
- Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z);
- Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized();
- Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z);
+/**
+ * attach_new_node
+ * configures node, assigns parent node
+**/
+void EditorSceneImporterAssimp::attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform) {
+ ERR_FAIL_COND(new_node == NULL);
+ ERR_FAIL_COND(node == NULL);
+ ERR_FAIL_COND(parent_node == NULL);
+ ERR_FAIL_COND(state.root == NULL);
+
+ // assign properties to new godot note
+ new_node->set_name(Name);
+ new_node->set_transform(transform);
+
+ // add element as child to parent
+ parent_node->add_child(new_node);
+
+ // owner must be set after
+ new_node->set_owner(state.root);
+
+ // cache node mapping results by name and then by aiNode*
+ state.node_map[Name] = new_node;
+ state.assimp_node_map[node] = new_node;
+}
+/**
+ * Create a light for the scene
+ * Automatically caches lights for lookup later
+ */
+void EditorSceneImporterAssimp::create_light(ImportState &state, RecursiveState &recursive_state) {
+ Light *light = NULL;
+ aiLight *ai_light = state.assimp_scene->mLights[state.light_cache[recursive_state.node_name]];
+ ERR_FAIL_COND(!ai_light);
+
+ if (ai_light->mType == aiLightSource_DIRECTIONAL) {
+ light = memnew(DirectionalLight);
+ Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
+ dir.normalize();
+ Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
+ Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z);
+ up.normalize();
+
+ Transform light_transform;
+ light_transform.set_look_at(pos, pos + dir, up);
+
+ recursive_state.node_transform *= light_transform;
+
+ } else if (ai_light->mType == aiLightSource_POINT) {
+ light = memnew(OmniLight);
+ Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
Transform xform;
- xform.set_look_at(pos, look_at, up);
-
- new_node = camera;
- } else if (state.bone_owners.has(node_name)) {
-
- //have to actually put the skeleton somewhere, you know.
- Skeleton *skeleton = state.skeletons[state.bone_owners[node_name]];
- if (skeleton->get_parent()) {
- //a bone for a skeleton already added..
- //could go downwards here to add meshes children of skeleton bones
- //but let's not support it for now.
- return;
- }
- //restore rest poses to local, now that we know where the skeleton finally is
- Transform skeleton_transform;
- if (p_assimp_node->mParent) {
- skeleton_transform = _get_global_assimp_node_transform(p_assimp_node->mParent);
- }
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- Transform rest = skeleton_transform.affine_inverse() * skeleton->get_bone_rest(i);
- skeleton->set_bone_rest(i, rest.affine_inverse());
- }
+ xform.origin = pos;
- skeleton->localize_rests();
- node_name = "Skeleton"; //don't use the bone root name
- node_transform = Transform(); //don't transform
+ recursive_state.node_transform *= xform;
- new_node = skeleton;
- } else {
- //generic node
- new_node = memnew(Spatial);
- }
+ light->set_transform(xform);
- {
+ //light->set_param(Light::PARAM_ATTENUATION, 1);
+ } else if (ai_light->mType == aiLightSource_SPOT) {
+ light = memnew(SpotLight);
- new_node->set_name(node_name);
- new_node->set_transform(node_transform);
- p_parent->add_child(new_node);
- new_node->set_owner(state.root);
- }
+ Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
+ dir.normalize();
+ Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
+ Vector3 up = Vector3(ai_light->mUp.x, ai_light->mUp.y, ai_light->mUp.z);
+ up.normalize();
- state.node_map[node_name] = new_node;
+ Transform light_transform;
+ light_transform.set_look_at(pos, pos + dir, up);
+ recursive_state.node_transform *= light_transform;
- for (size_t i = 0; i < p_assimp_node->mNumChildren; i++) {
- _generate_node(state, p_assimp_node->mChildren[i], new_node);
+ //light->set_param(Light::PARAM_ATTENUATION, 0.0f);
}
-}
+ ERR_FAIL_COND(light == NULL);
-void EditorSceneImporterAssimp::_calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w) {
- const aiVector3D normals = ai_mesh->mAnimMeshes[i]->mNormals[tri_index];
- const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z);
- const aiVector3D tangent = ai_mesh->mAnimMeshes[i]->mTangents[tri_index];
- const Vector3 godot_tangent = Vector3(tangent.x, tangent.y, tangent.z);
- const aiVector3D bitangent = ai_mesh->mAnimMeshes[i]->mBitangents[tri_index];
- const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z);
- float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f;
- Color plane_tangent = Color(tangent.x, tangent.y, tangent.z, d);
- w[index] = plane_tangent;
+ light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b));
+ recursive_state.new_node = light;
+
+ attach_new_node(state,
+ recursive_state.new_node,
+ recursive_state.assimp_node,
+ recursive_state.parent_node,
+ recursive_state.node_name,
+ recursive_state.node_transform);
}
-void EditorSceneImporterAssimp::_set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture) {
- ERR_FAIL_COND(map_mode == NULL);
- aiTextureMapMode tex_mode = aiTextureMapMode::aiTextureMapMode_Wrap;
- //for (size_t i = 0; i < 3; i++) {
- tex_mode = map_mode[0];
- //}
- int32_t flags = Texture::FLAGS_DEFAULT;
- if (tex_mode == aiTextureMapMode_Wrap) {
- //Default
- } else if (tex_mode == aiTextureMapMode_Clamp) {
- flags = flags & ~Texture::FLAG_REPEAT;
- } else if (tex_mode == aiTextureMapMode_Mirror) {
- flags = flags | Texture::FLAG_MIRRORED_REPEAT;
+/**
+ * Create camera for the scene
+ */
+void EditorSceneImporterAssimp::create_camera(ImportState &state, RecursiveState &recursive_state) {
+ aiCamera *ai_camera = state.assimp_scene->mCameras[state.camera_cache[recursive_state.node_name]];
+ ERR_FAIL_COND(!ai_camera);
+
+ Camera *camera = memnew(Camera);
+
+ float near = ai_camera->mClipPlaneNear;
+ if (Math::is_equal_approx(near, 0.0f)) {
+ near = 0.1f;
}
- texture->set_flags(flags);
-}
+ camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar);
-void EditorSceneImporterAssimp::_find_texture_path(const String &r_p_path, String &r_path, bool &r_found) {
+ Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z);
+ Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized();
+ Vector3 up = Vector3(ai_camera->mUp.x, ai_camera->mUp.y, ai_camera->mUp.z);
- _Directory dir;
+ Transform xform;
+ xform.set_look_at(pos, look_at, up);
- List<String> exts;
- ImageLoader::get_recognized_extensions(&exts);
+ recursive_state.new_node = camera;
- Vector<String> split_path = r_path.get_basename().split("*");
- if (split_path.size() == 2) {
- r_found = true;
- return;
- }
+ attach_new_node(state,
+ recursive_state.new_node,
+ recursive_state.assimp_node,
+ recursive_state.parent_node,
+ recursive_state.node_name,
+ recursive_state.node_transform);
+}
- if (dir.file_exists(r_p_path.get_base_dir() + r_path.get_file())) {
- r_path = r_p_path.get_base_dir() + r_path.get_file();
- r_found = true;
- return;
- }
+/**
+ * Create Bone
+ * Create a bone in the scene
+ */
+void EditorSceneImporterAssimp::create_bone(ImportState &state, RecursiveState &recursive_state) {
+ // for each armature node we must make a new skeleton but ensure it
+ // has a bone in the child to ensure we don't make too many
+ // the reason you must do this is because a skeleton exists per mesh?
+ // and duplicate bone names are very bad for determining what is going on.
+ aiBone *parent_bone_assimp = get_bone_by_name(state.assimp_scene, recursive_state.assimp_node->mParent->mName);
- for (int32_t i = 0; i < exts.size(); i++) {
- if (r_found) {
- return;
- }
- if (r_found == false) {
- _find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]);
+ // set to true when you want to use skeleton reference from cache.
+ bool do_not_create_armature = false;
+
+ // prevent more than one skeleton existing per mesh
+ // * multiple root bones have this
+ // * this simply filters the node out if it has already been added then references the skeleton so we know the actual skeleton for this node
+ for (Map<Skeleton *, const Spatial *>::Element *key_value_pair = state.armature_skeletons.front(); key_value_pair; key_value_pair = key_value_pair->next()) {
+ if (key_value_pair->value() == recursive_state.parent_node) {
+ // apply the skeleton for this mesh
+ recursive_state.skeleton = key_value_pair->key();
+
+ // force this off
+ do_not_create_armature = true;
}
}
-}
-void EditorSceneImporterAssimp::_find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension) {
- String name = path.get_basename() + extension;
- if (dir.file_exists(name)) {
- found = true;
- path = name;
- return;
- }
- String name_ignore_sub_directory = p_path.get_base_dir().plus_file(path.get_file().get_basename()) + extension;
- if (dir.file_exists(name_ignore_sub_directory)) {
- found = true;
- path = name_ignore_sub_directory;
- return;
- }
+ // check if parent was a bone
+ // if parent was not a bone this is the first bone.
+ // therefore parent is the 'armature'?
+ // also for multi root bone support make sure we don't already have the skeleton cached.
+ // if we do we must merge them - as this is all godot supports right now.
+ if (!parent_bone_assimp && recursive_state.skeleton == NULL && !do_not_create_armature) {
+ // create new skeleton on the root.
+ recursive_state.skeleton = memnew(Skeleton);
- String name_find_texture_sub_directory = p_path.get_base_dir() + "/textures/" + path.get_file().get_basename() + extension;
- if (dir.file_exists(name_find_texture_sub_directory)) {
- found = true;
- path = name_find_texture_sub_directory;
- return;
- }
- String name_find_texture_upper_sub_directory = p_path.get_base_dir() + "/Textures/" + path.get_file().get_basename() + extension;
- if (dir.file_exists(name_find_texture_upper_sub_directory)) {
- found = true;
- path = name_find_texture_upper_sub_directory;
- return;
- }
- String name_find_texture_outside_sub_directory = p_path.get_base_dir() + "/../textures/" + path.get_file().get_basename() + extension;
- if (dir.file_exists(name_find_texture_outside_sub_directory)) {
- found = true;
- path = name_find_texture_outside_sub_directory;
- return;
- }
+ ERR_FAIL_COND(state.root == NULL);
+ ERR_FAIL_COND(recursive_state.skeleton == NULL);
- String name_find_upper_texture_outside_sub_directory = p_path.get_base_dir() + "/../Textures/" + path.get_file().get_basename() + extension;
- if (dir.file_exists(name_find_upper_texture_outside_sub_directory)) {
- found = true;
- path = name_find_upper_texture_outside_sub_directory;
- return;
- }
-}
+ print_verbose("Parent armature node is called " + recursive_state.parent_node->get_name());
+ // store root node for this skeleton / used in animation playback and bone detection.
+
+ state.armature_skeletons.insert(recursive_state.skeleton, Object::cast_to<Spatial>(recursive_state.parent_node));
-String EditorSceneImporterAssimp::_assimp_get_string(const aiString &p_string) const {
- //convert an assimp String to a Godot String
- String name;
- name.parse_utf8(p_string.C_Str() /*,p_string.length*/);
- if (name.find(":") != -1) {
- String replaced_name = name.split(":")[1];
- print_verbose("Replacing " + name + " containing : with " + replaced_name);
- name = replaced_name;
+ //skeleton->set_use_bones_in_world_transform(true);
+ print_verbose("Created new FBX skeleton for armature node");
}
- name = name.replace(".", ""); //can break things, specially bone names
+ ERR_FAIL_COND_MSG(recursive_state.skeleton == NULL, "Mesh has invalid armature detection - report this");
- return name;
-}
+ // this transform is a bone
+ recursive_state.skeleton->add_bone(recursive_state.node_name);
-String EditorSceneImporterAssimp::_assimp_anim_string_to_string(const aiString &p_string) const {
+ ERR_FAIL_COND(recursive_state.skeleton == NULL); // serious bug we must now exit.
+ //ERR_FAIL_COND(recursive_state.skeleton->get_name() == "");
+ print_verbose("Bone added to lookup: " + AssimpUtils::get_assimp_string(recursive_state.bone->mName));
+ print_verbose("Skeleton attached to: " + recursive_state.skeleton->get_name());
+ // make sure to write the bone lookup inverse so we can retrieve the mesh for this bone later
+ state.bone_to_skeleton_lookup.insert(recursive_state.bone, recursive_state.skeleton);
- String name;
- name.parse_utf8(p_string.C_Str() /*,p_string.length*/);
- if (name.find(":") != -1) {
- String replaced_name = name.split(":")[1];
- print_verbose("Replacing " + name + " containing : with " + replaced_name);
- name = replaced_name;
+ Transform xform = AssimpUtils::assimp_matrix_transform(recursive_state.bone->mOffsetMatrix);
+ recursive_state.skeleton->set_bone_rest(recursive_state.skeleton->get_bone_count() - 1, xform.affine_inverse());
+
+ // get parent node of assimp node
+ const aiNode *parent_node_assimp = recursive_state.assimp_node->mParent;
+
+ // ensure we have a parent
+ if (parent_node_assimp != NULL) {
+ int parent_bone_id = recursive_state.skeleton->find_bone(AssimpUtils::get_assimp_string(parent_node_assimp->mName));
+ int current_bone_id = recursive_state.skeleton->find_bone(recursive_state.node_name);
+ print_verbose("Parent bone id " + itos(parent_bone_id) + " current bone id" + itos(current_bone_id));
+ print_verbose("Bone debug: " + AssimpUtils::get_assimp_string(parent_node_assimp->mName));
+ recursive_state.skeleton->set_bone_parent(current_bone_id, parent_bone_id);
}
- return name;
}
-String EditorSceneImporterAssimp::_assimp_raw_string_to_string(const aiString &p_string) const {
- String name;
- name.parse_utf8(p_string.C_Str() /*,p_string.length*/);
- return name;
-}
+/**
+ * Generate node
+ * Recursive call to iterate over all nodes
+ */
+void EditorSceneImporterAssimp::_generate_node(
+ ImportState &state,
+ Skeleton *skeleton,
+ const aiNode *assimp_node, Node *parent_node) {
+
+ // sanity check
+ ERR_FAIL_COND(state.root == NULL);
+ ERR_FAIL_COND(state.assimp_scene == NULL);
+ ERR_FAIL_COND(assimp_node == NULL);
+ ERR_FAIL_COND(parent_node == NULL);
-Ref<Animation> EditorSceneImporterAssimp::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) {
- return Ref<Animation>();
-}
+ Spatial *new_node = NULL;
+ String node_name = AssimpUtils::get_assimp_string(assimp_node->mName);
+ Transform node_transform = AssimpUtils::assimp_matrix_transform(assimp_node->mTransformation);
-const Transform EditorSceneImporterAssimp::_assimp_matrix_transform(const aiMatrix4x4 p_matrix) {
- aiMatrix4x4 matrix = p_matrix;
- Transform xform;
- //xform.set(matrix.a1, matrix.b1, matrix.c1, matrix.a2, matrix.b2, matrix.c2, matrix.a3, matrix.b3, matrix.c3, matrix.a4, matrix.b4, matrix.c4);
- xform.set(matrix.a1, matrix.a2, matrix.a3, matrix.b1, matrix.b2, matrix.b3, matrix.c1, matrix.c2, matrix.c3, matrix.a4, matrix.b4, matrix.c4);
- return xform;
-}
+ // can safely return null - is this node a bone?
+ aiBone *bone = get_bone_by_name(state.assimp_scene, assimp_node->mName);
+
+ // out arguments helper - for pushing state down into creation functions
+ RecursiveState recursive_state(node_transform, skeleton, new_node, node_name, assimp_node, parent_node, bone);
+
+ // Creation code
+ if (state.light_cache.has(node_name)) {
+ create_light(state, recursive_state);
+ } else if (state.camera_cache.has(node_name)) {
+ create_camera(state, recursive_state);
+ } else if (bone != NULL) {
+ create_bone(state, recursive_state);
+ } else {
+ //generic node
+ recursive_state.new_node = memnew(Spatial);
+ attach_new_node(state,
+ recursive_state.new_node,
+ recursive_state.assimp_node,
+ recursive_state.parent_node,
+ recursive_state.node_name,
+ recursive_state.node_transform);
+ }
+
+ // recurse into all child elements
+ for (size_t i = 0; i < recursive_state.assimp_node->mNumChildren; i++) {
+ _generate_node(state, recursive_state.skeleton, recursive_state.assimp_node->mChildren[i],
+ recursive_state.new_node != NULL ? recursive_state.new_node : recursive_state.parent_node);
+ }
+} \ No newline at end of file
diff --git a/modules/assimp/editor_scene_importer_assimp.h b/modules/assimp/editor_scene_importer_assimp.h
index 7a30816e3b..787376c9af 100644
--- a/modules/assimp/editor_scene_importer_assimp.h
+++ b/modules/assimp/editor_scene_importer_assimp.h
@@ -44,60 +44,31 @@
#include "scene/resources/animation.h"
#include "scene/resources/surface_tool.h"
-#include "assimp/DefaultLogger.hpp"
-#include "assimp/LogStream.hpp"
-#include "assimp/Logger.hpp"
-#include "assimp/matrix4x4.h"
-#include "assimp/scene.h"
-#include "assimp/types.h"
+#include <assimp/matrix4x4.h>
+#include <assimp/scene.h>
+#include <assimp/types.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/LogStream.hpp>
+#include <assimp/Logger.hpp>
+
+#include "import_state.h"
+#include "import_utils.h"
+
+using namespace AssimpImporter;
class AssimpStream : public Assimp::LogStream {
public:
// Constructor
- AssimpStream();
+ AssimpStream() {}
// Destructor
- ~AssimpStream();
+ ~AssimpStream() {}
// Write something using your own functionality
- void write(const char *message);
+ void write(const char *message) {
+ print_verbose(String("Open Asset Import: ") + String(message).strip_edges());
+ }
};
-#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor", 0, 0
-#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness", 0, 0
-#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness", 0, 0
-
-#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0
-
-#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0
-
-#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling", 0, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color", 0, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive", 0, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic", 0, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness", 0, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity", 0, 0
-
-#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file", aiTextureType_UNKNOWN, 0
-#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo", aiTextureType_UNKNOWN, 0
-
class EditorSceneImporterAssimp : public EditorSceneImporter {
private:
GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter);
@@ -112,59 +83,6 @@ private:
};
};
- struct AssetImportFbx {
- enum ETimeMode {
- TIME_MODE_DEFAULT = 0,
- TIME_MODE_120 = 1,
- TIME_MODE_100 = 2,
- TIME_MODE_60 = 3,
- TIME_MODE_50 = 4,
- TIME_MODE_48 = 5,
- TIME_MODE_30 = 6,
- TIME_MODE_30_DROP = 7,
- TIME_MODE_NTSC_DROP_FRAME = 8,
- TIME_MODE_NTSC_FULL_FRAME = 9,
- TIME_MODE_PAL = 10,
- TIME_MODE_CINEMA = 11,
- TIME_MODE_1000 = 12,
- TIME_MODE_CINEMA_ND = 13,
- TIME_MODE_CUSTOM = 14,
- TIME_MODE_TIME_MODE_COUNT = 15
- };
- enum UpAxis {
- UP_VECTOR_AXIS_X = 1,
- UP_VECTOR_AXIS_Y = 2,
- UP_VECTOR_AXIS_Z = 3
- };
- enum FrontAxis {
- FRONT_PARITY_EVEN = 1,
- FRONT_PARITY_ODD = 2,
- };
-
- enum CoordAxis {
- COORD_RIGHT = 0,
- COORD_LEFT = 1
- };
- };
-
- struct ImportState {
-
- String path;
- const aiScene *assimp_scene;
- uint32_t max_bone_weights;
- Spatial *root;
- Map<String, Ref<Mesh> > mesh_cache;
- Map<int, Ref<Material> > material_cache;
- Map<String, int> light_cache;
- Map<String, int> camera_cache;
- Vector<Skeleton *> skeletons;
- Map<String, int> bone_owners; //maps bones to skeleton index owned by
- Map<String, Node *> node_map;
- Map<MeshInstance *, Skeleton *> mesh_skeletons;
- bool fbx; //for some reason assimp does some things different for FBX
- AnimationPlayer *animation_player;
- };
-
struct BoneInfo {
uint32_t bone;
float weight;
@@ -177,28 +95,29 @@ private:
const aiNode *node;
};
- const Transform _assimp_matrix_transform(const aiMatrix4x4 p_matrix);
- String _assimp_get_string(const aiString &p_string) const;
- Transform _get_global_assimp_node_transform(const aiNode *p_current_node);
-
void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w);
void _set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture);
- void _find_texture_path(const String &p_path, String &path, bool &r_found);
- void _find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension);
-
- Ref<Texture> _load_texture(ImportState &state, String p_path);
- Ref<Material> _generate_material_from_index(ImportState &state, int p_index, bool p_double_sided);
- Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, Skeleton *p_skeleton = NULL, bool p_double_sided_material = false);
- void _generate_node(ImportState &state, const aiNode *p_assimp_node, Node *p_parent);
- void _generate_bone_groups(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<String, Transform> &bind_xforms);
- void _fill_node_relationships(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, int p_skeleton_id, Skeleton *p_skeleton, const String &p_parent_name, int &holecount, const Vector<SkeletonHole> &p_holes, const Map<String, Transform> &bind_xforms);
- void _generate_skeletons(ImportState &state, const aiNode *p_assimp_node, Map<String, int> &ownership, Map<int, int> &skeleton_map, const Map<String, Transform> &bind_xforms);
+ Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, const aiNode *assimp_node, Skeleton *p_skeleton = NULL);
+
+ // utility for node creation
+ void attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform);
+ // simple object creation functions
+ void create_light(ImportState &state, RecursiveState &recursive_state);
+ void create_camera(ImportState &state, RecursiveState &recursive_state);
+ void create_bone(ImportState &state, RecursiveState &recursive_state);
+ // non recursive - linear so must not use recursive arguments
+ void create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform);
+
+ // recursive node generator
+ void _generate_node(ImportState &state, Skeleton *skeleton, const aiNode *assimp_node, Node *parent_node);
+ // runs after _generate_node as it must then use pre-created godot skeleton.
+ void generate_mesh_phase_from_skeletal_mesh(ImportState &state);
void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name);
void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps);
- Spatial *_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights);
+ Spatial *_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights);
String _assimp_anim_string_to_string(const aiString &p_string) const;
String _assimp_raw_string_to_string(const aiString &p_string) const;
@@ -228,7 +147,7 @@ public:
virtual void get_extensions(List<String> *r_extensions) const;
virtual uint32_t get_import_flags() const;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = NULL);
- virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps);
+ Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path);
};
#endif
#endif
diff --git a/modules/assimp/import_state.h b/modules/assimp/import_state.h
new file mode 100644
index 0000000000..8d82cd3e39
--- /dev/null
+++ b/modules/assimp/import_state.h
@@ -0,0 +1,115 @@
+/*************************************************************************/
+/* import_state.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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 EDITOR_SCENE_IMPORT_STATE_H
+#define EDITOR_SCENE_IMPORT_STATE_H
+
+#include "core/bind/core_bind.h"
+#include "core/io/resource_importer.h"
+#include "core/vector.h"
+#include "editor/import/resource_importer_scene.h"
+#include "editor/project_settings_editor.h"
+#include "scene/3d/mesh_instance.h"
+#include "scene/3d/skeleton.h"
+#include "scene/3d/spatial.h"
+#include "scene/animation/animation_player.h"
+#include "scene/resources/animation.h"
+#include "scene/resources/surface_tool.h"
+
+#include <assimp/matrix4x4.h>
+#include <assimp/scene.h>
+#include <assimp/types.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/LogStream.hpp>
+#include <assimp/Logger.hpp>
+
+namespace AssimpImporter {
+/** Import state is for global scene import data
+ * This makes the code simpler and contains useful lookups.
+ */
+struct ImportState {
+
+ String path;
+ const aiScene *assimp_scene;
+ uint32_t max_bone_weights;
+
+ Spatial *root;
+ Map<String, Ref<Mesh> > mesh_cache;
+ Map<int, Ref<Material> > material_cache;
+ Map<String, int> light_cache;
+ Map<String, int> camera_cache;
+ //Vector<Skeleton *> skeletons;
+ Map<Skeleton *, const Spatial *> armature_skeletons; // maps skeletons based on their armature nodes.
+ Map<const aiBone *, Skeleton *> bone_to_skeleton_lookup; // maps bones back into their skeleton
+ // very useful for when you need to ask assimp for the bone mesh
+ Map<String, Node *> node_map;
+ Map<const aiNode *, const Node *> assimp_node_map;
+ Map<String, Ref<Image> > path_to_image_cache;
+ bool fbx; //for some reason assimp does some things different for FBX
+ AnimationPlayer *animation_player;
+};
+
+struct AssimpImageData {
+ Ref<Image> raw_image;
+ Ref<ImageTexture> texture;
+ aiTextureMapMode *map_mode = NULL;
+};
+
+/** Recursive state is used to push state into functions instead of specifying them
+ * This makes the code easier to handle too and add extra arguments without breaking things
+ */
+struct RecursiveState {
+ RecursiveState(
+ Transform &_node_transform,
+ Skeleton *_skeleton,
+ Spatial *_new_node,
+ const String &_node_name,
+ const aiNode *_assimp_node,
+ Node *_parent_node,
+ const aiBone *_bone) :
+ node_transform(_node_transform),
+ skeleton(_skeleton),
+ new_node(_new_node),
+ node_name(_node_name),
+ assimp_node(_assimp_node),
+ parent_node(_parent_node),
+ bone(_bone) {}
+
+ Transform &node_transform;
+ Skeleton *skeleton;
+ Spatial *new_node;
+ const String &node_name;
+ const aiNode *assimp_node;
+ Node *parent_node;
+ const aiBone *bone;
+};
+} // namespace AssimpImporter
+
+#endif // EDITOR_SCENE_IMPORT_STATE_H
diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h
new file mode 100644
index 0000000000..4be76ade0f
--- /dev/null
+++ b/modules/assimp/import_utils.h
@@ -0,0 +1,448 @@
+/*************************************************************************/
+/* import_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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 IMPORT_UTILS_IMPORTER_ASSIMP_H
+#define IMPORT_UTILS_IMPORTER_ASSIMP_H
+
+#include "core/io/image_loader.h"
+#include "import_state.h"
+
+#include <assimp/SceneCombiner.h>
+#include <assimp/cexport.h>
+#include <assimp/cimport.h>
+#include <assimp/matrix4x4.h>
+#include <assimp/pbrmaterial.h>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <assimp/LogStream.hpp>
+#include <assimp/Logger.hpp>
+#include <string>
+
+using namespace AssimpImporter;
+
+#define AI_PROPERTIES aiTextureType_UNKNOWN, 0
+#define AI_NULL 0, 0
+#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor"
+#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness"
+#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness"
+
+#define AI_MATKEY_FBX_MAYA_EMISSION_TEXTURE "$raw.Maya|emissionColor|file"
+#define AI_MATKEY_FBX_MAYA_EMISSIVE_FACTOR "$raw.Maya|emission"
+#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file"
+#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file"
+#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file"
+#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file"
+#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo"
+
+#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file"
+#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo"
+
+#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity"
+
+#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file"
+#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo"
+
+/**
+ * Assimp Utils
+ * Conversion tools / glue code to convert from assimp to godot
+*/
+class AssimpUtils {
+public:
+ /**
+ * calculate tangents for mesh data from assimp data
+ */
+ static void calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w) {
+ const aiVector3D normals = ai_mesh->mAnimMeshes[i]->mNormals[tri_index];
+ const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z);
+ const aiVector3D tangent = ai_mesh->mAnimMeshes[i]->mTangents[tri_index];
+ const Vector3 godot_tangent = Vector3(tangent.x, tangent.y, tangent.z);
+ const aiVector3D bitangent = ai_mesh->mAnimMeshes[i]->mBitangents[tri_index];
+ const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z);
+ float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f;
+ Color plane_tangent = Color(tangent.x, tangent.y, tangent.z, d);
+ w[index] = plane_tangent;
+ }
+
+ struct AssetImportFbx {
+ enum ETimeMode {
+ TIME_MODE_DEFAULT = 0,
+ TIME_MODE_120 = 1,
+ TIME_MODE_100 = 2,
+ TIME_MODE_60 = 3,
+ TIME_MODE_50 = 4,
+ TIME_MODE_48 = 5,
+ TIME_MODE_30 = 6,
+ TIME_MODE_30_DROP = 7,
+ TIME_MODE_NTSC_DROP_FRAME = 8,
+ TIME_MODE_NTSC_FULL_FRAME = 9,
+ TIME_MODE_PAL = 10,
+ TIME_MODE_CINEMA = 11,
+ TIME_MODE_1000 = 12,
+ TIME_MODE_CINEMA_ND = 13,
+ TIME_MODE_CUSTOM = 14,
+ TIME_MODE_TIME_MODE_COUNT = 15
+ };
+ enum UpAxis {
+ UP_VECTOR_AXIS_X = 1,
+ UP_VECTOR_AXIS_Y = 2,
+ UP_VECTOR_AXIS_Z = 3
+ };
+ enum FrontAxis {
+ FRONT_PARITY_EVEN = 1,
+ FRONT_PARITY_ODD = 2,
+ };
+
+ enum CoordAxis {
+ COORD_RIGHT = 0,
+ COORD_LEFT = 1
+ };
+ };
+
+ /** Get assimp string
+ * automatically filters the string data
+ */
+ static String get_assimp_string(const aiString &p_string) {
+ //convert an assimp String to a Godot String
+ String name;
+ name.parse_utf8(p_string.C_Str() /*,p_string.length*/);
+ if (name.find(":") != -1) {
+ String replaced_name = name.split(":")[1];
+ print_verbose("Replacing " + name + " containing : with " + replaced_name);
+ name = replaced_name;
+ }
+
+ return name;
+ }
+
+ static String get_anim_string_from_assimp(const aiString &p_string) {
+
+ String name;
+ name.parse_utf8(p_string.C_Str() /*,p_string.length*/);
+ if (name.find(":") != -1) {
+ String replaced_name = name.split(":")[1];
+ print_verbose("Replacing " + name + " containing : with " + replaced_name);
+ name = replaced_name;
+ }
+ return name;
+ }
+
+ /**
+ * No filter logic get_raw_string_from_assimp
+ * This just convers the aiString to a parsed utf8 string
+ * Without removing special chars etc
+ */
+ static String get_raw_string_from_assimp(const aiString &p_string) {
+ String name;
+ name.parse_utf8(p_string.C_Str() /*,p_string.length*/);
+ return name;
+ }
+
+ static Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) {
+ return Ref<Animation>();
+ }
+
+ /**
+ * Converts aiMatrix4x4 to godot Transform
+ */
+ static const Transform assimp_matrix_transform(const aiMatrix4x4 p_matrix) {
+ aiMatrix4x4 matrix = p_matrix;
+ Transform xform;
+ xform.set(matrix.a1, matrix.a2, matrix.a3, matrix.b1, matrix.b2, matrix.b3, matrix.c1, matrix.c2, matrix.c3, matrix.a4, matrix.b4, matrix.c4);
+ return xform;
+ }
+
+ /** Get fbx fps for time mode meta data
+ */
+ static float get_fbx_fps(int32_t time_mode, const aiScene *p_scene) {
+ switch (time_mode) {
+ case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack
+ case AssetImportFbx::TIME_MODE_120: return 120;
+ case AssetImportFbx::TIME_MODE_100: return 100;
+ case AssetImportFbx::TIME_MODE_60: return 60;
+ case AssetImportFbx::TIME_MODE_50: return 50;
+ case AssetImportFbx::TIME_MODE_48: return 48;
+ case AssetImportFbx::TIME_MODE_30: return 30;
+ case AssetImportFbx::TIME_MODE_30_DROP: return 30;
+ case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME: return 29.9700262f;
+ case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME: return 29.9700262f;
+ case AssetImportFbx::TIME_MODE_PAL: return 25;
+ case AssetImportFbx::TIME_MODE_CINEMA: return 24;
+ case AssetImportFbx::TIME_MODE_1000: return 1000;
+ case AssetImportFbx::TIME_MODE_CINEMA_ND: return 23.976f;
+ case AssetImportFbx::TIME_MODE_CUSTOM:
+ int32_t frame_rate = -1;
+ p_scene->mMetaData->Get("FrameRate", frame_rate);
+ return frame_rate;
+ }
+ return 0;
+ }
+
+ /**
+ * Get global transform for the current node - so we can use world space rather than
+ * local space coordinates
+ * useful if you need global - although recommend using local wherever possible over global
+ * as you could break fbx scaling :)
+ */
+ static Transform _get_global_assimp_node_transform(const aiNode *p_current_node) {
+ aiNode const *current_node = p_current_node;
+ Transform xform;
+ while (current_node != NULL) {
+ xform = assimp_matrix_transform(current_node->mTransformation) * xform;
+ current_node = current_node->mParent;
+ }
+ return xform;
+ }
+
+ /**
+ * Find hardcoded textures from assimp which could be in many different directories
+ */
+ static void find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension) {
+ Vector<String> paths;
+ paths.push_back(path.get_basename() + extension);
+ paths.push_back(path + extension);
+ paths.push_back(path);
+ paths.push_back(p_path.get_base_dir().plus_file(path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file(path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file(path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("textures/" + path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("Textures/" + path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../Textures/" + path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../textures/" + path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("texture/" + path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("Texture/" + path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../Texture/" + path.get_file()));
+ paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file().get_basename() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file() + extension));
+ paths.push_back(p_path.get_base_dir().plus_file("../texture/" + path.get_file()));
+ for (int i = 0; i < paths.size(); i++) {
+ if (dir.file_exists(paths[i])) {
+ found = true;
+ path = paths[i];
+ return;
+ }
+ }
+ }
+
+ /** find the texture path for the supplied fbx path inside godot
+ * very simple lookup for subfolders etc for a texture which may or may not be in a directory
+ */
+ static void find_texture_path(const String &r_p_path, String &r_path, bool &r_found) {
+ _Directory dir;
+
+ List<String> exts;
+ ImageLoader::get_recognized_extensions(&exts);
+
+ Vector<String> split_path = r_path.get_basename().split("*");
+ if (split_path.size() == 2) {
+ r_found = true;
+ return;
+ }
+
+ if (dir.file_exists(r_p_path.get_base_dir() + r_path.get_file())) {
+ r_path = r_p_path.get_base_dir() + r_path.get_file();
+ r_found = true;
+ return;
+ }
+
+ for (int32_t i = 0; i < exts.size(); i++) {
+ if (r_found) {
+ return;
+ }
+ if (r_found == false) {
+ find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]);
+ }
+ }
+ }
+
+ /**
+ * set_texture_mapping_mode
+ * Helper to check the mapping mode of the texture (repeat, clamp and mirror)
+ */
+ static void set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<ImageTexture> texture) {
+ ERR_FAIL_COND(texture.is_null());
+ ERR_FAIL_COND(map_mode == NULL);
+ aiTextureMapMode tex_mode = aiTextureMapMode::aiTextureMapMode_Wrap;
+
+ tex_mode = map_mode[0];
+
+ int32_t flags = Texture::FLAGS_DEFAULT;
+ if (tex_mode == aiTextureMapMode_Wrap) {
+ //Default
+ } else if (tex_mode == aiTextureMapMode_Clamp) {
+ flags = flags & ~Texture::FLAG_REPEAT;
+ } else if (tex_mode == aiTextureMapMode_Mirror) {
+ flags = flags | Texture::FLAG_MIRRORED_REPEAT;
+ }
+ texture->set_flags(flags);
+ }
+
+ /**
+ * Load or load from cache image :)
+ */
+ static Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path) {
+
+ Map<String, Ref<Image> >::Element *match = state.path_to_image_cache.find(p_path);
+
+ // if our cache contains this image then don't bother
+ if (match) {
+ return match->get();
+ }
+
+ Vector<String> split_path = p_path.get_basename().split("*");
+ if (split_path.size() == 2) {
+ size_t texture_idx = split_path[1].to_int();
+ ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Image>());
+ aiTexture *tex = p_scene->mTextures[texture_idx];
+ String filename = AssimpUtils::get_raw_string_from_assimp(tex->mFilename);
+ filename = filename.get_file();
+ print_verbose("Open Asset Import: Loading embedded texture " + filename);
+ if (tex->mHeight == 0) {
+ if (tex->CheckFormat("png")) {
+ Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
+ ERR_FAIL_COND_V(img.is_null(), Ref<Image>());
+ state.path_to_image_cache.insert(p_path, img);
+ return img;
+ } else if (tex->CheckFormat("jpg")) {
+ Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
+ ERR_FAIL_COND_V(img.is_null(), Ref<Image>());
+ state.path_to_image_cache.insert(p_path, img);
+ return img;
+ } else if (tex->CheckFormat("dds")) {
+ ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented");
+ ERR_FAIL_COND_V(true, Ref<Image>());
+ }
+ } else {
+ Ref<Image> img;
+ img.instance();
+ PoolByteArray arr;
+ uint32_t size = tex->mWidth * tex->mHeight;
+ arr.resize(size);
+ memcpy(arr.write().ptr(), tex->pcData, size);
+ ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Image>());
+ //ARGB8888 to RGBA8888
+ for (int32_t i = 0; i < arr.size() / 4; i++) {
+ arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0];
+ arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1];
+ arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2];
+ arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3];
+ }
+ img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr);
+ ERR_FAIL_COND_V(img.is_null(), Ref<Image>());
+ state.path_to_image_cache.insert(p_path, img);
+ return img;
+ }
+ return Ref<Image>();
+ } else {
+ Ref<Texture> texture = ResourceLoader::load(p_path);
+ Ref<Image> image = texture->get_data();
+ state.path_to_image_cache.insert(p_path, image);
+ return image;
+ }
+
+ return Ref<Image>();
+ }
+
+ /* create texture from assimp data, if found in path */
+ static bool CreateAssimpTexture(
+ AssimpImporter::ImportState &state,
+ aiString texture_path,
+ String &filename,
+ String &path,
+ AssimpImageData &image_state) {
+ filename = get_raw_string_from_assimp(texture_path);
+ path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
+ bool found = false;
+ find_texture_path(state.path, path, found);
+ if (found) {
+ image_state.raw_image = AssimpUtils::load_image(state, state.assimp_scene, path);
+ if (image_state.raw_image.is_valid()) {
+ image_state.texture.instance();
+ image_state.texture->create_from_image(image_state.raw_image);
+ image_state.texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
+ return true;
+ }
+ }
+
+ return false;
+ }
+ /** GetAssimpTexture
+ * Designed to retrieve textures for you
+ */
+ static bool GetAssimpTexture(
+ AssimpImporter::ImportState &state,
+ aiMaterial *ai_material,
+ aiTextureType texture_type,
+ String &filename,
+ String &path,
+ AssimpImageData &image_state) {
+ aiString ai_filename = aiString();
+ if (AI_SUCCESS == ai_material->GetTexture(texture_type, 0, &ai_filename, NULL, NULL, NULL, NULL, image_state.map_mode)) {
+ return CreateAssimpTexture(state, ai_filename, filename, path, image_state);
+ }
+
+ return false;
+ }
+};
+
+#endif // IMPORT_UTILS_IMPORTER_ASSIMP_H
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 7c01e85ff7..925dbda620 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2826,6 +2826,16 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS);
options.insert(option.display, option);
}
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
+ }
}
List<StringName> native_classes;
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index ad8bf5b2c0..97790e00bb 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -106,6 +106,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
"typeof",
"type_exists",
"char",
+ "ord",
"str",
"print",
"printt",
@@ -665,6 +666,33 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
CharType result[2] = { *p_args[0], 0 };
r_ret = String(result);
} break;
+ case TEXT_ORD: {
+
+ VALIDATE_ARG_COUNT(1);
+
+ if (p_args[0]->get_type() != Variant::STRING) {
+
+ r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::STRING;
+ r_ret = Variant();
+ return;
+ }
+
+ String str = p_args[0]->operator String();
+
+ if (str.length() != 1) {
+
+ r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::STRING;
+ r_ret = RTR("Expected a string of length 1 (a character).");
+ return;
+ }
+
+ r_ret = str.get(0);
+
+ } break;
case TEXT_STR: {
if (p_arg_count < 1) {
r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
@@ -1507,6 +1535,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) {
case TYPE_OF:
case TYPE_EXISTS:
case TEXT_CHAR:
+ case TEXT_ORD:
case TEXT_STR:
case COLOR8:
case LEN:
@@ -1849,6 +1878,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
+ case TEXT_ORD: {
+
+ MethodInfo mi("ord", PropertyInfo(Variant::STRING, "char"));
+ mi.return_val.type = Variant::INT;
+ return mi;
+
+ } break;
case TEXT_STR: {
MethodInfo mi("str");
diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h
index 8f7ba76d2c..9ea5dd46cf 100644
--- a/modules/gdscript/gdscript_functions.h
+++ b/modules/gdscript/gdscript_functions.h
@@ -97,6 +97,7 @@ public:
TYPE_OF,
TYPE_EXISTS,
TEXT_CHAR,
+ TEXT_ORD,
TEXT_STR,
TEXT_PRINT,
TEXT_PRINT_TABBED,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 99bfdae7ec..e96bf0238a 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -252,6 +252,16 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
}
+ // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented.
+ int next_valid_offset = 1;
+ if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) {
+ next_valid_offset++;
+ // There is a chunk of the identifier that also needs to be ignored (not always there!)
+ if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) {
+ next_valid_offset++;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
//subexpression ()
tokenizer->advance();
@@ -668,7 +678,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
expr = cn;
}
- } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
+ } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
//function or constructor
@@ -5245,6 +5255,31 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive
return;
}
p = NULL;
+ } else {
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == base) {
+ String singleton_path = ProjectSettings::get_singleton()->get(s);
+ if (singleton_path.begins_with("*")) {
+ singleton_path = singleton_path.right(1);
+ }
+ if (!singleton_path.begins_with("res://")) {
+ singleton_path = "res://" + singleton_path;
+ }
+ base_script = ResourceLoader::load(singleton_path);
+ if (!base_script.is_valid()) {
+ _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
+ return;
+ }
+ p = NULL;
+ }
+ }
}
while (p) {
@@ -5589,9 +5624,49 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,
}
name_part++;
continue;
- } else {
- p = current_class;
}
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ String singleton_path;
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == id) {
+ singleton_path = ProjectSettings::get_singleton()->get(s);
+ if (singleton_path.begins_with("*")) {
+ singleton_path = singleton_path.right(1);
+ }
+ if (!singleton_path.begins_with("res://")) {
+ singleton_path = "res://" + singleton_path;
+ }
+ break;
+ }
+ }
+ if (!singleton_path.empty()) {
+ Ref<Script> script = ResourceLoader::load(singleton_path);
+ Ref<GDScript> gds = script;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ } else if (script.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = script;
+ } else {
+ _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line);
+ return DataType();
+ }
+ name_part++;
+ continue;
+ }
+
+ p = current_class;
} else if (base_type.kind == DataType::CLASS) {
p = base_type.class_type;
}
@@ -6510,7 +6585,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
return DataType();
}
}
- if (check_types && !node_type.has_type) {
+ if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) {
// Can infer indexing type for some variant types
DataType result;
result.has_type = true;
diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp
index b18b48d1b0..e1bba60f2f 100644
--- a/modules/jsonrpc/jsonrpc.cpp
+++ b/modules/jsonrpc/jsonrpc.cpp
@@ -47,11 +47,11 @@ void JSONRPC::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_notification", "method", "params"), &JSONRPC::make_notification);
ClassDB::bind_method(D_METHOD("make_response_error", "code", "message", "id"), &JSONRPC::make_response_error, DEFVAL(Variant()));
- BIND_ENUM_CONSTANT(ParseError)
- BIND_ENUM_CONSTANT(InvalidRequest)
- BIND_ENUM_CONSTANT(MethodNotFound)
- BIND_ENUM_CONSTANT(InvalidParams)
- BIND_ENUM_CONSTANT(InternalError)
+ BIND_ENUM_CONSTANT(PARSE_ERROR)
+ BIND_ENUM_CONSTANT(INVALID_REQUEST)
+ BIND_ENUM_CONSTANT(METHOD_NOT_FOUND)
+ BIND_ENUM_CONSTANT(INVALID_PARAMS)
+ BIND_ENUM_CONSTANT(INTERNAL_ERROR)
}
Dictionary JSONRPC::make_response_error(int p_code, const String &p_message, const Variant &p_id) const {
@@ -120,7 +120,7 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem
}
if (object == NULL || !object->has_method(method)) {
- ret = make_response_error(JSONRPC::MethodNotFound, "Method not found", id);
+ ret = make_response_error(JSONRPC::METHOD_NOT_FOUND, "Method not found", id);
} else {
Variant call_ret = object->callv(method, args);
if (id.get_type() != Variant::NIL) {
@@ -138,10 +138,10 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem
}
ret = arr_ret;
} else {
- ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request");
+ ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request");
}
} else {
- ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request");
+ ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request");
}
return ret;
}
@@ -155,7 +155,7 @@ String JSONRPC::process_string(const String &p_input) {
String err_message;
int err_line;
if (OK != JSON::parse(p_input, input, err_message, err_line)) {
- ret = make_response_error(JSONRPC::ParseError, "Parse error");
+ ret = make_response_error(JSONRPC::PARSE_ERROR, "Parse error");
} else {
ret = process_action(input, true);
}
diff --git a/modules/jsonrpc/jsonrpc.h b/modules/jsonrpc/jsonrpc.h
index bcb34ecc65..91897d0b55 100644
--- a/modules/jsonrpc/jsonrpc.h
+++ b/modules/jsonrpc/jsonrpc.h
@@ -47,11 +47,11 @@ public:
~JSONRPC();
enum ErrorCode {
- ParseError = -32700,
- InvalidRequest = -32600,
- MethodNotFound = -32601,
- InvalidParams = -32602,
- InternalError = -32603,
+ PARSE_ERROR = -32700,
+ INVALID_REQUEST = -32600,
+ METHOD_NOT_FOUND = -32601,
+ INVALID_PARAMS = -32602,
+ INTERNAL_ERROR = -32603,
};
Dictionary make_response_error(int p_code, const String &p_message, const Variant &p_id = Variant()) const;
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 2cd05b5c50..15423f8c5e 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -1944,6 +1944,7 @@ TileMap::TileMap() {
quadrant_order_dirty = false;
quadrant_size = 16;
cell_size = Size2(64, 64);
+ custom_transform = Transform2D(64, 0, 0, 64, 0, 0);
collision_layer = 1;
collision_mask = 1;
friction = 1;
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index 75f5f79873..09ef6f26bf 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -241,9 +241,13 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) {
float newofs = CLAMP(x / float(total_w), 0, 1);
- //Snap to nearest point if holding shift
- if (mm->get_shift()) {
- float snap_threshold = 0.03;
+ // Snap to "round" coordinates if holding Ctrl.
+ // Be more precise if holding Shift as well
+ if (mm->get_control()) {
+ newofs = Math::stepify(newofs, mm->get_shift() ? 0.025 : 0.1);
+ } else if (mm->get_shift()) {
+ // Snap to nearest point if holding just Shift
+ const float snap_threshold = 0.03;
float smallest_ofs = snap_threshold;
bool found = false;
int nearest_point = 0;
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 461281a4ed..a840e3fec1 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -382,7 +382,10 @@ void ScrollContainer::update_scrollbars() {
Size2 min = child_max_size;
- if (!scroll_v || min.height <= size.height - hmin.height) {
+ bool hide_scroll_v = !scroll_v || min.height <= size.height - hmin.height;
+ bool hide_scroll_h = !scroll_h || min.width <= size.width - vmin.width;
+
+ if (hide_scroll_v) {
v_scroll->hide();
v_scroll->set_max(0);
@@ -391,11 +394,16 @@ void ScrollContainer::update_scrollbars() {
v_scroll->show();
v_scroll->set_max(min.height);
- v_scroll->set_page(size.height - hmin.height);
+ if (hide_scroll_h) {
+ v_scroll->set_page(size.height);
+ } else {
+ v_scroll->set_page(size.height - hmin.height);
+ }
+
scroll.y = v_scroll->get_value();
}
- if (!scroll_h || min.width <= size.width - vmin.width) {
+ if (hide_scroll_h) {
h_scroll->hide();
h_scroll->set_max(0);
@@ -404,7 +412,12 @@ void ScrollContainer::update_scrollbars() {
h_scroll->show();
h_scroll->set_max(min.width);
- h_scroll->set_page(size.width - vmin.width);
+ if (hide_scroll_v) {
+ h_scroll->set_page(size.width);
+ } else {
+ h_scroll->set_page(size.width - vmin.width);
+ }
+
scroll.x = h_scroll->get_value();
}
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index ab5ed3166c..0464cc1ac8 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -611,7 +611,6 @@ void TextEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
-
_update_caches();
if (cursor_changed_dirty)
MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit");
@@ -620,12 +619,16 @@ void TextEdit::_notification(int p_what) {
_update_wrap_at();
} break;
case NOTIFICATION_RESIZED: {
-
_update_scrollbars();
_update_wrap_at();
} break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (is_visible()) {
+ call_deferred("_update_scrollbars");
+ call_deferred("_update_wrap_at");
+ }
+ } break;
case NOTIFICATION_THEME_CHANGED: {
-
_update_caches();
_update_wrap_at();
syntax_highlighting_cache.clear();
@@ -5362,6 +5365,9 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l
break;
}
pos_from = last_pos - p_key.length();
+ if (pos_from < 0) {
+ break;
+ }
}
} else {
while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.find(p_key, pos_from) : text_line.findn(p_key, pos_from)) != -1) {
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 617a703855..6c89016ea3 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -33,6 +33,7 @@
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
#include "core/message_queue.h"
+#include "core/os/dir_access.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/print_string.h"
@@ -1953,6 +1954,38 @@ bool SceneTree::is_using_font_oversampling() const {
return use_font_oversampling;
}
+void SceneTree::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
+
+ if (p_function == "change_scene") {
+ DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ List<String> directories;
+ directories.push_back(dir_access->get_current_dir());
+
+ while (!directories.empty()) {
+ dir_access->change_dir(directories.back()->get());
+ directories.pop_back();
+
+ dir_access->list_dir_begin();
+ String filename = dir_access->get_next();
+
+ while (filename != "") {
+ if (filename == "." || filename == "..") {
+ filename = dir_access->get_next();
+ continue;
+ }
+
+ if (dir_access->dir_exists(filename)) {
+ directories.push_back(dir_access->get_current_dir().plus_file(filename));
+ } else if (filename.ends_with(".tscn") || filename.ends_with(".scn")) {
+ r_options->push_back("\"" + dir_access->get_current_dir().plus_file(filename) + "\"");
+ }
+
+ filename = dir_access->get_next();
+ }
+ }
+ }
+}
+
SceneTree::SceneTree() {
if (singleton == NULL) singleton = this;
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 42a87545a6..d387886d61 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -408,6 +408,7 @@ public:
void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
void global_menu_action(const Variant &p_id, const Variant &p_meta);
+ void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
//network API
diff --git a/thirdparty/assimp/assimp/config.h b/thirdparty/assimp/assimp/config.h
index 382a698268..d0e4817349 100644
--- a/thirdparty/assimp/assimp/config.h
+++ b/thirdparty/assimp/assimp/config.h
@@ -983,6 +983,13 @@ enum aiComponent {
#define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f
#endif // !! AI_DEBONE_THRESHOLD
+#define AI_CONFIG_APP_SCALE_KEY "APP_SCALE_FACTOR"
+
+#if (!defined AI_CONFIG_APP_SCALE_KEY)
+# define AI_CONFIG_APP_SCALE_KEY 1.0
+#endif // AI_CONFIG_APP_SCALE_KEY
+
+
// ---------- All the Build/Compile-time defines ------------
/** @brief Specifies if double precision is supported inside assimp
diff --git a/thirdparty/assimp/code/Common/BaseImporter.cpp b/thirdparty/assimp/code/Common/BaseImporter.cpp
index 0a5694aa0e..de5018a250 100644
--- a/thirdparty/assimp/code/Common/BaseImporter.cpp
+++ b/thirdparty/assimp/code/Common/BaseImporter.cpp
@@ -76,9 +76,25 @@ BaseImporter::~BaseImporter() {
// nothing to do here
}
+void BaseImporter::UpdateImporterScale( Importer* pImp )
+{
+ ai_assert(pImp != nullptr);
+ ai_assert(importerScale != 0.0);
+ ai_assert(fileScale != 0.0);
+
+ double activeScale = importerScale * fileScale;
+
+ // Set active scaling
+ pImp->SetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, activeScale);
+
+ ASSIMP_LOG_DEBUG_F("UpdateImporterScale scale set: %f", activeScale );
+}
+
// ------------------------------------------------------------------------------------------------
// Imports the given file and returns the imported data.
-aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) {
+aiScene* BaseImporter::ReadFile(Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) {
+
+
m_progress = pImp->GetProgressHandler();
if (nullptr == m_progress) {
return nullptr;
@@ -100,6 +116,11 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile,
{
InternReadFile( pFile, sc.get(), &filter);
+ // Calculate import scale hook - required because pImp not available anywhere else
+ // passes scale into ScaleProcess
+ UpdateImporterScale(pImp);
+
+
} catch( const std::exception& err ) {
// extract error description
m_ErrorText = err.what();
@@ -112,7 +133,7 @@ aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile,
}
// ------------------------------------------------------------------------------------------------
-void BaseImporter::SetupProperties(const Importer* /*pImp*/)
+void BaseImporter::SetupProperties(const Importer* pImp)
{
// the default implementation does nothing
}
@@ -588,6 +609,8 @@ aiScene* BatchLoader::GetImport( unsigned int which )
return nullptr;
}
+
+
// ------------------------------------------------------------------------------------------------
void BatchLoader::LoadAll()
{
diff --git a/thirdparty/assimp/code/FBX/FBXConverter.cpp b/thirdparty/assimp/code/FBX/FBXConverter.cpp
index 9f940d3226..9bd970098e 100644
--- a/thirdparty/assimp/code/FBX/FBXConverter.cpp
+++ b/thirdparty/assimp/code/FBX/FBXConverter.cpp
@@ -66,6 +66,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <vector>
#include <sstream>
#include <iomanip>
+#include <cstdint>
namespace Assimp {
@@ -90,7 +91,6 @@ namespace Assimp {
, anim_fps()
, out(out)
, doc(doc)
- , mRemoveEmptyBones( removeEmptyBones )
, mCurrentUnit(FbxUnit::cm) {
// animations need to be converted first since this will
// populate the node_anim_chain_bits map, which is needed
@@ -119,7 +119,6 @@ namespace Assimp {
ConvertGlobalSettings();
TransferDataToScene();
- ConvertToUnitScale(unit);
// if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
// to make sure the scene passes assimp's validation. FBX files
@@ -685,30 +684,37 @@ namespace Assimp {
bool ok;
aiMatrix4x4 chain[TransformationComp_MAXIMUM];
+
+ ai_assert(TransformationComp_MAXIMUM < 32);
+ std::uint32_t chainBits = 0;
+ // A node won't need a node chain if it only has these.
+ const std::uint32_t chainMaskSimple = (1 << TransformationComp_Translation) + (1 << TransformationComp_Scaling) + (1 << TransformationComp_Rotation);
+ // A node will need a node chain if it has any of these.
+ const std::uint32_t chainMaskComplex = ((1 << (TransformationComp_MAXIMUM)) - 1) - chainMaskSimple;
+
std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
// generate transformation matrices for all the different transformation components
const float zero_epsilon = 1e-6f;
const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
- bool is_complex = false;
const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
if (ok && PreRotation.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_PreRotation);
GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]);
}
const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
if (ok && PostRotation.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_PostRotation);
GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]);
}
const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok);
if (ok && RotationPivot.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_RotationPivot) | (1 << TransformationComp_RotationPivotInverse);
aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]);
aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]);
@@ -716,21 +722,21 @@ namespace Assimp {
const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok);
if (ok && RotationOffset.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_RotationOffset);
aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]);
}
const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok);
if (ok && ScalingOffset.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_ScalingOffset);
aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]);
}
const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok);
if (ok && ScalingPivot.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_ScalingPivot) | (1 << TransformationComp_ScalingPivotInverse);
aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]);
aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]);
@@ -738,22 +744,28 @@ namespace Assimp {
const aiVector3D& Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok);
if (ok && Translation.SquareLength() > zero_epsilon) {
+ chainBits = chainBits | (1 << TransformationComp_Translation);
+
aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]);
}
const aiVector3D& Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok);
if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) {
+ chainBits = chainBits | (1 << TransformationComp_Scaling);
+
aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]);
}
const aiVector3D& Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok);
if (ok && Rotation.SquareLength() > zero_epsilon) {
+ chainBits = chainBits | (1 << TransformationComp_Rotation);
+
GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
}
const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_GeometricScaling);
aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
aiVector3D GeometricScalingInverse = GeometricScaling;
bool canscale = true;
@@ -768,13 +780,14 @@ namespace Assimp {
}
}
if (canscale) {
+ chainBits = chainBits | (1 << TransformationComp_GeometricScalingInverse);
aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]);
}
}
const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
if (ok && GeometricRotation.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_GeometricRotation) | (1 << TransformationComp_GeometricRotationInverse);
GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]);
GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]);
chain[TransformationComp_GeometricRotationInverse].Inverse();
@@ -782,7 +795,7 @@ namespace Assimp {
const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
if (ok && GeometricTranslation.SquareLength() > zero_epsilon) {
- is_complex = true;
+ chainBits = chainBits | (1 << TransformationComp_GeometricTranslation) | (1 << TransformationComp_GeometricTranslationInverse);
aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]);
aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]);
}
@@ -790,12 +803,12 @@ namespace Assimp {
// is_complex needs to be consistent with NeedsComplexTransformationChain()
// or the interplay between this code and the animation converter would
// not be guaranteed.
- ai_assert(NeedsComplexTransformationChain(model) == is_complex);
+ ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0));
// now, if we have more than just Translation, Scaling and Rotation,
// we need to generate a full node chain to accommodate for assimp's
// lack to express pivots and offsets.
- if (is_complex && doc.Settings().preservePivots) {
+ if ((chainBits & chainMaskComplex) && doc.Settings().preservePivots) {
FBXImporter::LogInfo("generating full transformation chain for node: " + name);
// query the anim_chain_bits dictionary to find out which chain elements
@@ -808,7 +821,7 @@ namespace Assimp {
for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
const TransformationComp comp = static_cast<TransformationComp>(i);
- if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) {
+ if ((chainBits & bit) == 0 && (anim_chain_bitmask & bit) == 0) {
continue;
}
@@ -1462,14 +1475,8 @@ namespace Assimp {
const WeightIndexArray& indices = cluster->GetIndices();
- if (indices.empty() && mRemoveEmptyBones ) {
- continue;
- }
-
const MatIndexArray& mats = geo.GetMaterialIndices();
- bool ok = false;
-
const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
count_out_indices.clear();
@@ -1509,8 +1516,7 @@ namespace Assimp {
out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
}
- ++count_out_indices.back();
- ok = true;
+ ++count_out_indices.back();
}
}
}
@@ -1518,10 +1524,8 @@ namespace Assimp {
// if we found at least one, generate the output bones
// XXX this could be heavily simplified by collecting the bone
// data in a single step.
- if (ok && mRemoveEmptyBones) {
- ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
+ ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
count_out_indices, node_global_transform);
- }
}
}
catch (std::exception&) {
@@ -3532,46 +3536,6 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate());
}
- void FBXConverter::ConvertToUnitScale( FbxUnit unit ) {
- if (mCurrentUnit == unit) {
- return;
- }
-
- ai_real scale = 1.0;
- if (mCurrentUnit == FbxUnit::cm) {
- if (unit == FbxUnit::m) {
- scale = (ai_real)0.01;
- } else if (unit == FbxUnit::km) {
- scale = (ai_real)0.00001;
- }
- } else if (mCurrentUnit == FbxUnit::m) {
- if (unit == FbxUnit::cm) {
- scale = (ai_real)100.0;
- } else if (unit == FbxUnit::km) {
- scale = (ai_real)0.001;
- }
- } else if (mCurrentUnit == FbxUnit::km) {
- if (unit == FbxUnit::cm) {
- scale = (ai_real)100000.0;
- } else if (unit == FbxUnit::m) {
- scale = (ai_real)1000.0;
- }
- }
-
- for (auto mesh : meshes) {
- if (nullptr == mesh) {
- continue;
- }
-
- if (mesh->HasPositions()) {
- for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
- aiVector3D &pos = mesh->mVertices[i];
- pos *= scale;
- }
- }
- }
- }
-
void FBXConverter::TransferDataToScene()
{
ai_assert(!out->mMeshes);
diff --git a/thirdparty/assimp/code/FBX/FBXConverter.h b/thirdparty/assimp/code/FBX/FBXConverter.h
index 17a7bc56b7..b458627392 100644
--- a/thirdparty/assimp/code/FBX/FBXConverter.h
+++ b/thirdparty/assimp/code/FBX/FBXConverter.h
@@ -431,10 +431,6 @@ private:
void ConvertGlobalSettings();
// ------------------------------------------------------------------------------------------------
- // Will perform the conversion from a given unit to the requested unit.
- void ConvertToUnitScale(FbxUnit unit);
-
- // ------------------------------------------------------------------------------------------------
// copy generated meshes, animations, lights, cameras and textures to the output scene
void TransferDataToScene();
@@ -470,9 +466,6 @@ private:
aiScene* const out;
const FBX::Document& doc;
-
- bool mRemoveEmptyBones;
-
FbxUnit mCurrentUnit;
};
diff --git a/thirdparty/assimp/code/FBX/FBXDocument.cpp b/thirdparty/assimp/code/FBX/FBXDocument.cpp
index 1af08fe6d8..506fd978dd 100644
--- a/thirdparty/assimp/code/FBX/FBXDocument.cpp
+++ b/thirdparty/assimp/code/FBX/FBXDocument.cpp
@@ -90,14 +90,6 @@ const Object* LazyObject::Get(bool dieOnError)
return object.get();
}
- // if this is the root object, we return a dummy since there
- // is no root object int he fbx file - it is just referenced
- // with id 0.
- if(id == 0L) {
- object.reset(new Object(id, element, "Model::RootNode"));
- return object.get();
- }
-
const Token& key = element.KeyToken();
const TokenList& tokens = element.Tokens();
diff --git a/thirdparty/assimp/code/FBX/FBXExporter.cpp b/thirdparty/assimp/code/FBX/FBXExporter.cpp
index 153e676506..8ebc8555a2 100644
--- a/thirdparty/assimp/code/FBX/FBXExporter.cpp
+++ b/thirdparty/assimp/code/FBX/FBXExporter.cpp
@@ -1706,8 +1706,7 @@ void FBXExporter::WriteObjects ()
}
if (end) { break; }
}
- limbnodes.insert(parent);
- skeleton.insert(parent);
+
// if it was the skeleton root we can finish here
if (end) { break; }
}
@@ -1848,44 +1847,10 @@ void FBXExporter::WriteObjects ()
inverse_bone_xform.Inverse();
aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
- // this should be the same as the bone's mOffsetMatrix.
- // if it's not the same, the skeleton isn't in the bind pose.
- float epsilon = 1e-4f; // some error is to be expected
- float epsilon_custom = mProperties->GetPropertyFloat("BINDPOSE_EPSILON", -1);
- if(epsilon_custom > 0)
- epsilon = epsilon_custom;
- bool bone_xform_okay = true;
- if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) {
- not_in_bind_pose.insert(b);
- bone_xform_okay = false;
- }
+ sdnode.AddChild("Transform", tr);
- // if we have a bone we should use the mOffsetMatrix,
- // otherwise try to just use the calculated transform.
- if (b) {
- sdnode.AddChild("Transform", b->mOffsetMatrix);
- } else {
- sdnode.AddChild("Transform", tr);
- }
- // note: it doesn't matter if we mix these,
- // because if they disagree we'll throw an exception later.
- // it could be that the skeleton is not in the bone pose
- // but all bones are still defined,
- // in which case this would use the mOffsetMatrix for everything
- // and a correct skeleton would still be output.
-
- // transformlink should be the position of the bone in world space.
- // if the bone is in the bind pose (or nonexistent),
- // we can just use the matrix we already calculated
- if (bone_xform_okay) {
- sdnode.AddChild("TransformLink", bone_xform);
- // otherwise we can only work it out using the mesh position.
- } else {
- aiMatrix4x4 trl = b->mOffsetMatrix;
- trl.Inverse();
- trl *= mesh_xform;
- sdnode.AddChild("TransformLink", trl);
- }
+
+ sdnode.AddChild("TransformLink", bone_xform);
// note: this means we ALWAYS rely on the mesh node transform
// being unchanged from the time the skeleton was bound.
// there's not really any way around this at the moment.
diff --git a/thirdparty/assimp/code/FBX/FBXImporter.cpp b/thirdparty/assimp/code/FBX/FBXImporter.cpp
index ec8bbd2b47..bd359dbf29 100644
--- a/thirdparty/assimp/code/FBX/FBXImporter.cpp
+++ b/thirdparty/assimp/code/FBX/FBXImporter.cpp
@@ -189,8 +189,16 @@ void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOS
if (settings.convertToMeters) {
unit = FbxUnit::m;
}
+
// convert the FBX DOM to aiScene
- ConvertToAssimpScene(pScene,doc, settings.removeEmptyBones, unit);
+ ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones, unit);
+
+ // size relative to cm
+ float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor();
+
+ // Set FBX file scale is relative to CM must be converted to M for
+ // assimp universal format (M)
+ SetFileScale( size_relative_to_cm * 0.01f);
std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
}
diff --git a/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp b/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp
index 44a0264ca0..5c9a0e309d 100644
--- a/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp
+++ b/thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp
@@ -115,7 +115,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
if(tempVerts.empty()) {
FBXImporter::LogWarn("encountered mesh with no vertices");
- return;
}
std::vector<int> tempFaces;
@@ -123,7 +122,6 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
if(tempFaces.empty()) {
FBXImporter::LogWarn("encountered mesh with no faces");
- return;
}
m_vertices.reserve(tempFaces.size());
@@ -612,7 +610,10 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
const std::string& ReferenceInformationType)
{
const size_t face_count = m_faces.size();
- ai_assert(face_count);
+ if(face_count <= 0)
+ {
+ return;
+ }
// materials are handled separately. First of all, they are assigned per-face
// and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect
diff --git a/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp b/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp
index b30f39c274..a3f7dd2557 100644
--- a/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp
+++ b/thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp
@@ -212,7 +212,7 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
// project tangent and bitangent into the plane formed by the vertex' normal
aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
- localTangent.Normalize(); localBitangent.Normalize();
+ localTangent.NormalizeSafe(); localBitangent.NormalizeSafe();
// reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z);
@@ -220,10 +220,10 @@ bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
if (invalid_tangent != invalid_bitangent) {
if (invalid_tangent) {
localTangent = meshNorm[p] ^ localBitangent;
- localTangent.Normalize();
+ localTangent.NormalizeSafe();
} else {
localBitangent = localTangent ^ meshNorm[p];
- localBitangent.Normalize();
+ localBitangent.NormalizeSafe();
}
}
diff --git a/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp b/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp
index 6d458c4b11..ac770c41f2 100644
--- a/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp
+++ b/thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp
@@ -39,19 +39,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
-#ifndef ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS
-
#include "ScaleProcess.h"
#include <assimp/scene.h>
#include <assimp/postprocess.h>
+#include <assimp/BaseImporter.h>
namespace Assimp {
ScaleProcess::ScaleProcess()
: BaseProcess()
, mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) {
- // empty
}
ScaleProcess::~ScaleProcess() {
@@ -71,10 +69,26 @@ bool ScaleProcess::IsActive( unsigned int pFlags ) const {
}
void ScaleProcess::SetupProperties( const Importer* pImp ) {
- mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 0 );
+ // User scaling
+ mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 1.0f );
+
+ // File scaling * Application Scaling
+ float importerScale = pImp->GetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, 1.0f );
+
+ // apply scale to the scale
+ // helps prevent bugs with backward compatibility for anyone using normal scaling.
+ mScale *= importerScale;
}
void ScaleProcess::Execute( aiScene* pScene ) {
+ if(mScale == 1.0f) {
+ return; // nothing to scale
+ }
+
+ ai_assert( mScale != 0 );
+ ai_assert( nullptr != pScene );
+ ai_assert( nullptr != pScene->mRootNode );
+
if ( nullptr == pScene ) {
return;
}
@@ -82,22 +96,113 @@ void ScaleProcess::Execute( aiScene* pScene ) {
if ( nullptr == pScene->mRootNode ) {
return;
}
+
+ // Process animations and update position transform to new unit system
+ for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ )
+ {
+ aiAnimation* animation = pScene->mAnimations[animationID];
+
+ for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++)
+ {
+ aiNodeAnim* anim = animation->mChannels[animationChannel];
+
+ for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++)
+ {
+ aiVectorKey& vectorKey = anim->mPositionKeys[posKey];
+ vectorKey.mValue *= mScale;
+ }
+ }
+ }
+
+ for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++)
+ {
+ aiMesh *mesh = pScene->mMeshes[meshID];
+
+ // Reconstruct mesh vertexes to the new unit system
+ for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++)
+ {
+ aiVector3D& vertex = mesh->mVertices[vertexID];
+ vertex *= mScale;
+ }
+
+
+ // bone placement / scaling
+ for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++)
+ {
+ // Reconstruct matrix by transform rather than by scale
+ // This prevent scale values being changed which can
+ // be meaningful in some cases
+ // like when you want the modeller to see 1:1 compatibility.
+ aiBone* bone = mesh->mBones[boneID];
+
+ aiVector3D pos, scale;
+ aiQuaternion rotation;
+
+ bone->mOffsetMatrix.Decompose( scale, rotation, pos);
+
+ aiMatrix4x4 translation;
+ aiMatrix4x4::Translation( pos * mScale, translation );
+
+ aiMatrix4x4 scaling;
+ aiMatrix4x4::Scaling( aiVector3D(scale), scaling );
+
+ aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix());
+
+ bone->mOffsetMatrix = translation * RotMatrix * scaling;
+ }
+
+
+ // animation mesh processing
+ // convert by position rather than scale.
+ for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++)
+ {
+ aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID];
+
+ for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++)
+ {
+ aiVector3D& vertex = animMesh->mVertices[vertexID];
+ vertex *= mScale;
+ }
+ }
+ }
traverseNodes( pScene->mRootNode );
}
-void ScaleProcess::traverseNodes( aiNode *node ) {
+void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) {
applyScaling( node );
+
+ for( size_t i = 0; i < node->mNumChildren; i++)
+ {
+ // recurse into the tree until we are done!
+ traverseNodes( node->mChildren[i], nested_node_id+1 );
+ }
}
void ScaleProcess::applyScaling( aiNode *currentNode ) {
if ( nullptr != currentNode ) {
- currentNode->mTransformation.a1 = currentNode->mTransformation.a1 * mScale;
- currentNode->mTransformation.b2 = currentNode->mTransformation.b2 * mScale;
- currentNode->mTransformation.c3 = currentNode->mTransformation.c3 * mScale;
+ // Reconstruct matrix by transform rather than by scale
+ // This prevent scale values being changed which can
+ // be meaningful in some cases
+ // like when you want the modeller to
+ // see 1:1 compatibility.
+
+ aiVector3D pos, scale;
+ aiQuaternion rotation;
+ currentNode->mTransformation.Decompose( scale, rotation, pos);
+
+ aiMatrix4x4 translation;
+ aiMatrix4x4::Translation( pos * mScale, translation );
+
+ aiMatrix4x4 scaling;
+
+ // note: we do not use mScale here, this is on purpose.
+ aiMatrix4x4::Scaling( scale, scaling );
+
+ aiMatrix4x4 RotMatrix = aiMatrix4x4 (rotation.GetMatrix());
+
+ currentNode->mTransformation = translation * RotMatrix * scaling;
}
}
} // Namespace Assimp
-
-#endif // !! ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS
diff --git a/thirdparty/assimp/code/PostProcessing/ScaleProcess.h b/thirdparty/assimp/code/PostProcessing/ScaleProcess.h
index 2567378759..468a216736 100644
--- a/thirdparty/assimp/code/PostProcessing/ScaleProcess.h
+++ b/thirdparty/assimp/code/PostProcessing/ScaleProcess.h
@@ -39,7 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------
*/
-#pragma once
+#ifndef SCALE_PROCESS_H_
+#define SCALE_PROCESS_H_
#include "Common/BaseProcess.h"
@@ -53,6 +54,11 @@ namespace Assimp {
// ---------------------------------------------------------------------------
/** ScaleProcess: Class to rescale the whole model.
+ * Now rescales animations, bones, and blend shapes properly.
+ * Please note this will not write to 'scale' transform it will rewrite mesh
+ * and matrixes so that your scale values
+ * from your model package are preserved, so this is completely intentional
+ * bugs should be reported as soon as they are found.
*/
class ASSIMP_API ScaleProcess : public BaseProcess {
public:
@@ -78,7 +84,7 @@ public:
virtual void Execute( aiScene* pScene );
private:
- void traverseNodes( aiNode *currentNode );
+ void traverseNodes( aiNode *currentNode, unsigned int nested_node_id = 0 );
void applyScaling( aiNode *currentNode );
private:
@@ -86,3 +92,6 @@ private:
};
} // Namespace Assimp
+
+
+#endif // SCALE_PROCESS_H_ \ No newline at end of file
diff --git a/thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes b/thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes
new file mode 100644
index 0000000000..364411a23d
--- /dev/null
+++ b/thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes
@@ -0,0 +1,12 @@
+utf8 cpp library
+Release 2.3.4
+
+A minor bug fix release. Thanks to all who reported bugs.
+
+Note: Version 2.3.3 contained a regression, and therefore was removed.
+
+Changes from version 2.3.2
+- Bug fix [39]: checked.h Line 273 and unchecked.h Line 182 have an extra ';'
+- Bug fix [36]: replace_invalid() only works with back_inserter
+
+Files included in the release: utf8.h, core.h, checked.h, unchecked.h, utf8cpp.html, ReleaseNotes
diff --git a/thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html b/thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html
new file mode 100644
index 0000000000..6f2aacbe7b
--- /dev/null
+++ b/thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html
@@ -0,0 +1,1789 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <meta name="generator" content=
+ "HTML Tidy for Linux/x86 (vers 1st November 2002), see www.w3.org">
+ <meta name="description" content=
+ "A simple, portable and lightweigt C++ library for easy handling of UTF-8 encoded strings">
+ <meta name="keywords" content="UTF-8 C++ portable utf8 unicode generic templates">
+ <meta name="author" content="Nemanja Trifunovic">
+ <title>
+ UTF8-CPP: UTF-8 with C++ in a Portable Way
+ </title>
+ <style type="text/css">
+ <!--
+ span.return_value {
+ color: brown;
+ }
+ span.keyword {
+ color: blue;
+ }
+ span.preprocessor {
+ color: navy;
+ }
+ span.literal {
+ color: olive;
+ }
+ span.comment {
+ color: green;
+ }
+ code {
+ font-weight: bold;
+ }
+ ul.toc {
+ list-style-type: none;
+ }
+ p.version {
+ font-size: small;
+ font-style: italic;
+ }
+ -->
+ </style>
+ </head>
+ <body>
+ <h1>
+ UTF8-CPP: UTF-8 with C++ in a Portable Way
+ </h1>
+ <p>
+ <a href="https://sourceforge.net/projects/utfcpp">The Sourceforge project page</a>
+ </p>
+ <div id="toc">
+ <h2>
+ Table of Contents
+ </h2>
+ <ul class="toc">
+ <li>
+ <a href="#introduction">Introduction</a>
+ </li>
+ <li>
+ <a href="#examples">Examples of Use</a>
+ <ul class="toc">
+ <li>
+ <a href=#introsample>Introductionary Sample </a>
+ </li>
+ <li>
+ <a href=#validfile>Checking if a file contains valid UTF-8 text</a>
+ </li>
+ <li>
+ <a href=#fixinvalid>Ensure that a string contains valid UTF-8 text</a>
+ </li>
+ </ul>
+ <li>
+ <a href="#reference">Reference</a>
+ <ul class="toc">
+ <li>
+ <a href="#funutf8">Functions From utf8 Namespace </a>
+ </li>
+ <li>
+ <a href="#typesutf8">Types From utf8 Namespace </a>
+ </li>
+ <li>
+ <a href="#fununchecked">Functions From utf8::unchecked Namespace </a>
+ </li>
+ <li>
+ <a href="#typesunchecked">Types From utf8::unchecked Namespace </a>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <a href="#points">Points of Interest</a>
+ </li>
+ <li>
+ <a href="#links">Links</a>
+ </li>
+ </ul>
+ </div>
+ <h2 id="introduction">
+ Introduction
+ </h2>
+ <p>
+ Many C++ developers miss an easy and portable way of handling Unicode encoded
+ strings. The original C++ Standard (known as C++98 or C++03) is Unicode agnostic.
+ C++11 provides some support for Unicode on core language and library level:
+ u8, u, and U character and string literals, char16_t and char32_t character types,
+ u16string and u32string library classes, and codecvt support for conversions
+ between Unicode encoding forms.
+ In the meantime, developers use third party libraries like ICU, OS specific capabilities, or simply
+ roll out their own solutions.
+ </p>
+ <p>
+ In order to easily handle UTF-8 encoded Unicode strings, I came up with a small
+ generic library. For anybody used to work with STL algorithms and iterators, it should be
+ easy and natural to use. The code is freely available for any purpose - check out
+ the license at the beginning of the utf8.h file. If you run into
+ bugs or performance issues, please let me know and I'll do my best to address them.
+ </p>
+ <p>
+ The purpose of this article is not to offer an introduction to Unicode in general,
+ and UTF-8 in particular. If you are not familiar with Unicode, be sure to check out
+ <a href="http://www.unicode.org/">Unicode Home Page</a> or some other source of
+ information for Unicode. Also, it is not my aim to advocate the use of UTF-8
+ encoded strings in C++ programs; if you want to handle UTF-8 encoded strings from
+ C++, I am sure you have good reasons for it.
+ </p>
+ <h2 id="examples">
+ Examples of use
+ </h2>
+ <h3 id="introsample">
+ Introductionary Sample
+ </h3>
+ <p>
+ To illustrate the use of the library, let's start with a small but complete program
+ that opens a file containing UTF-8 encoded text, reads it line by line, checks each line
+ for invalid UTF-8 byte sequences, and converts it to UTF-16 encoding and back to UTF-8:
+ </p>
+<pre>
+<span class="preprocessor">#include &lt;fstream&gt;</span>
+<span class="preprocessor">#include &lt;iostream&gt;</span>
+<span class="preprocessor">#include &lt;string&gt;</span>
+<span class="preprocessor">#include &lt;vector&gt;</span>
+<span class="preprocessor">#include "utf8.h"</span>
+<span class="keyword">using namespace</span> std;
+<span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">char</span>** argv)
+{
+ <span class="keyword">if</span> (argc != <span class="literal">2</span>) {
+ cout &lt;&lt; <span class="literal">"\nUsage: docsample filename\n"</span>;
+ <span class="keyword">return</span> <span class="literal">0</span>;
+ }
+
+ <span class="keyword">const char</span>* test_file_path = argv[1];
+ <span class="comment">// Open the test file (contains UTF-8 encoded text)</span>
+ ifstream fs8(test_file_path);
+ <span class="keyword">if</span> (!fs8.is_open()) {
+ cout &lt;&lt; <span class=
+"literal">"Could not open "</span> &lt;&lt; test_file_path &lt;&lt; endl;
+ <span class="keyword">return</span> <span class="literal">0</span>;
+ }
+
+ <span class="keyword">unsigned</span> line_count = <span class="literal">1</span>;
+ string line;
+ <span class="comment">// Play with all the lines in the file</span>
+ <span class="keyword">while</span> (getline(fs8, line)) {
+ <span class="comment">// check for invalid utf-8 (for a simple yes/no check, there is also utf8::is_valid function)</span>
+ string::iterator end_it = utf8::find_invalid(line.begin(), line.end());
+ <span class="keyword">if</span> (end_it != line.end()) {
+ cout &lt;&lt; <span class=
+"literal">"Invalid UTF-8 encoding detected at line "</span> &lt;&lt; line_count &lt;&lt; <span
+ class="literal">"\n"</span>;
+ cout &lt;&lt; <span class=
+"literal">"This part is fine: "</span> &lt;&lt; string(line.begin(), end_it) &lt;&lt; <span
+ class="literal">"\n"</span>;
+ }
+
+ <span class="comment">// Get the line length (at least for the valid part)</span>
+ <span class="keyword">int</span> length = utf8::distance(line.begin(), end_it);
+ cout &lt;&lt; <span class=
+"literal">"Length of line "</span> &lt;&lt; line_count &lt;&lt; <span class=
+"literal">" is "</span> &lt;&lt; length &lt;&lt; <span class="literal">"\n"</span>;
+
+ <span class="comment">// Convert it to utf-16</span>
+ vector&lt;unsigned short&gt; utf16line;
+ utf8::utf8to16(line.begin(), end_it, back_inserter(utf16line));
+
+ <span class="comment">// And back to utf-8</span>
+ string utf8line;
+ utf8::utf16to8(utf16line.begin(), utf16line.end(), back_inserter(utf8line));
+
+ <span class="comment">// Confirm that the conversion went OK:</span>
+ <span class="keyword">if</span> (utf8line != string(line.begin(), end_it))
+ cout &lt;&lt; <span class=
+"literal">"Error in UTF-16 conversion at line: "</span> &lt;&lt; line_count &lt;&lt; <span
+ class="literal">"\n"</span>;
+
+ line_count++;
+ }
+ <span class="keyword">return</span> <span class="literal">0</span>;
+}
+</pre>
+ <p>
+ In the previous code sample, for each line we performed
+ a detection of invalid UTF-8 sequences with <code>find_invalid</code>; the number
+ of characters (more precisely - the number of Unicode code points, including the end
+ of line and even BOM if there is one) in each line was
+ determined with a use of <code>utf8::distance</code>; finally, we have converted
+ each line to UTF-16 encoding with <code>utf8to16</code> and back to UTF-8 with
+ <code>utf16to8</code>.
+ </p>
+ <h3 id="validfile">Checking if a file contains valid UTF-8 text</h3>
+<p>
+Here is a function that checks whether the content of a file is valid UTF-8 encoded text without
+reading the content into the memory:
+</p>
+<pre>
+<span class="keyword">bool</span> valid_utf8_file(i<span class="keyword">const char</span>* file_name)
+{
+ ifstream ifs(file_name);
+ <span class="keyword">if</span> (!ifs)
+ <span class="keyword">return false</span>; <span class="comment">// even better, throw here</span>
+
+ istreambuf_iterator&lt;<span class="keyword">char</span>&gt; it(ifs.rdbuf());
+ istreambuf_iterator&lt;<span class="keyword">char</span>&gt; eos;
+
+ <span class="keyword">return</span> utf8::is_valid(it, eos);
+}
+</pre>
+<p>
+Because the function <code>utf8::is_valid()</code> works with input iterators, we were able
+to pass an <code>istreambuf_iterator</code> to it and read the content of the file directly
+without loading it to the memory first.</p>
+<p>
+Note that other functions that take input iterator arguments can be used in a similar way. For
+instance, to read the content of a UTF-8 encoded text file and convert the text to UTF-16, just
+do something like:
+</p>
+<pre>
+ utf8::utf8to16(it, eos, back_inserter(u16string));
+</pre>
+ <h3 id="fixinvalid">Ensure that a string contains valid UTF-8 text</h3>
+<p>
+If we have some text that "probably" contains UTF-8 encoded text and we want to
+replace any invalid UTF-8 sequence with a replacement character, something like
+the following function may be used:
+</p>
+<pre>
+<span class="keyword">void</span> fix_utf8_string(std::string&amp; str)
+{
+ std::string temp;
+ utf8::replace_invalid(str.begin(), str.end(), back_inserter(temp));
+ str = temp;
+}
+</pre>
+<p>The function will replace any invalid UTF-8 sequence with a Unicode replacement character.
+There is an overloaded function that enables the caller to supply their own replacement character.
+</p>
+ <h2 id="reference">
+ Reference
+ </h2>
+ <h3 id="funutf8">
+ Functions From utf8 Namespace
+ </h3>
+ <h4>
+ utf8::append
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Encodes a 32 bit code point as a UTF-8 sequence of octets and appends the sequence
+ to a UTF-8 string.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+octet_iterator append(uint32_t cp, octet_iterator result);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an output iterator.<br>
+ <code>cp</code>: a 32 bit integer representing a code point to append to the
+ sequence.<br>
+ <code>result</code>: an output iterator to the place in the sequence where to
+ append the code point.<br>
+ <span class="return_value">Return value</span>: an iterator pointing to the place
+ after the newly appended sequence.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">unsigned char</span> u[<span class="literal">5</span>] = {<span
+class="literal">0</span>,<span class="literal">0</span>,<span class=
+"literal">0</span>,<span class="literal">0</span>,<span class="literal">0</span>};
+<span class="keyword">unsigned char</span>* end = append(<span class=
+"literal">0x0448</span>, u);
+assert (u[<span class="literal">0</span>] == <span class=
+"literal">0xd1</span> &amp;&amp; u[<span class="literal">1</span>] == <span class=
+"literal">0x88</span> &amp;&amp; u[<span class="literal">2</span>] == <span class=
+"literal">0</span> &amp;&amp; u[<span class="literal">3</span>] == <span class=
+"literal">0</span> &amp;&amp; u[<span class="literal">4</span>] == <span class=
+"literal">0</span>);
+</pre>
+ <p>
+ Note that <code>append</code> does not allocate any memory - it is the burden of
+ the caller to make sure there is enough memory allocated for the operation. To make
+ things more interesting, <code>append</code> can add anywhere between 1 and 4
+ octets to the sequence. In practice, you would most often want to use
+ <code>std::back_inserter</code> to ensure that the necessary memory is allocated.
+ </p>
+ <p>
+ In case of an invalid code point, a <code>utf8::invalid_code_point</code> exception
+ is thrown.
+ </p>
+ <h4>
+ utf8::next
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Given the iterator to the beginning of the UTF-8 sequence, it returns the code
+ point and moves the iterator to the next position.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t next(octet_iterator&amp; it, octet_iterator end);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>it</code>: a reference to an iterator pointing to the beginning of an UTF-8
+ encoded code point. After the function returns, it is incremented to point to the
+ beginning of the next code point.<br>
+ <code>end</code>: end of the UTF-8 sequence to be processed. If <code>it</code>
+ gets equal to <code>end</code> during the extraction of a code point, an
+ <code>utf8::not_enough_room</code> exception is thrown.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ processed UTF-8 code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">char</span>* w = twochars;
+<span class="keyword">int</span> cp = next(w, twochars + <span class="literal">6</span>);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars + <span class="literal">3</span>);
+</pre>
+ <p>
+ This function is typically used to iterate through a UTF-8 encoded string.
+ </p>
+ <p>
+ In case of an invalid UTF-8 seqence, a <code>utf8::invalid_utf8</code> exception is
+ thrown.
+ </p>
+ <h4>
+ utf8::peek_next
+ </h4>
+ <p class="version">
+ Available in version 2.1 and later.
+ </p>
+ <p>
+ Given the iterator to the beginning of the UTF-8 sequence, it returns the code
+ point for the following sequence without changing the value of the iterator.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t peek_next(octet_iterator it, octet_iterator end);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>it</code>: an iterator pointing to the beginning of an UTF-8
+ encoded code point.<br>
+ <code>end</code>: end of the UTF-8 sequence to be processed. If <code>it</code>
+ gets equal to <code>end</code> during the extraction of a code point, an
+ <code>utf8::not_enough_room</code> exception is thrown.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ processed UTF-8 code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">char</span>* w = twochars;
+<span class="keyword">int</span> cp = peek_next(w, twochars + <span class="literal">6</span>);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars);
+</pre>
+ <p>
+ In case of an invalid UTF-8 seqence, a <code>utf8::invalid_utf8</code> exception is
+ thrown.
+ </p>
+ <h4>
+ utf8::prior
+ </h4>
+ <p class="version">
+ Available in version 1.02 and later.
+ </p>
+ <p>
+ Given a reference to an iterator pointing to an octet in a UTF-8 sequence, it
+ decreases the iterator until it hits the beginning of the previous UTF-8 encoded
+ code point and returns the 32 bits representation of the code point.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t prior(octet_iterator&amp; it, octet_iterator start);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: a bidirectional iterator.<br>
+ <code>it</code>: a reference pointing to an octet within a UTF-8 encoded string.
+ After the function returns, it is decremented to point to the beginning of the
+ previous code point.<br>
+ <code>start</code>: an iterator to the beginning of the sequence where the search
+ for the beginning of a code point is performed. It is a
+ safety measure to prevent passing the beginning of the string in the search for a
+ UTF-8 lead octet.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ previous code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">unsigned char</span>* w = twochars + <span class=
+"literal">3</span>;
+<span class="keyword">int</span> cp = prior (w, twochars);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars);
+</pre>
+ <p>
+ This function has two purposes: one is two iterate backwards through a UTF-8
+ encoded string. Note that it is usually a better idea to iterate forward instead,
+ since <code>utf8::next</code> is faster. The second purpose is to find a beginning
+ of a UTF-8 sequence if we have a random position within a string. Note that in that
+ case <code>utf8::prior</code> may not detect an invalid UTF-8 sequence in some scenarios:
+ for instance if there are superfluous trail octets, it will just skip them.
+ </p>
+ <p>
+ <code>it</code> will typically point to the beginning of
+ a code point, and <code>start</code> will point to the
+ beginning of the string to ensure we don't go backwards too far. <code>it</code> is
+ decreased until it points to a lead UTF-8 octet, and then the UTF-8 sequence
+ beginning with that octet is decoded to a 32 bit representation and returned.
+ </p>
+ <p>
+ In case <code>start</code> is reached before a UTF-8 lead octet is hit, or if an
+ invalid UTF-8 sequence is started by the lead octet, an <code>invalid_utf8</code>
+ exception is thrown.
+ </p>
+ <p>In case <code>start</code> equals <code>it</code>, a <code>not_enough_room</code>
+ exception is thrown.
+ <h4>
+ utf8::previous
+ </h4>
+ <p class="version">
+ Deprecated in version 1.02 and later.
+ </p>
+ <p>
+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it
+ decreases the iterator until it hits the beginning of the previous UTF-8 encoded
+ code point and returns the 32 bits representation of the code point.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t previous(octet_iterator&amp; it, octet_iterator pass_start);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: a random access iterator.<br>
+ <code>it</code>: a reference pointing to an octet within a UTF-8 encoded string.
+ After the function returns, it is decremented to point to the beginning of the
+ previous code point.<br>
+ <code>pass_start</code>: an iterator to the point in the sequence where the search
+ for the beginning of a code point is aborted if no result was reached. It is a
+ safety measure to prevent passing the beginning of the string in the search for a
+ UTF-8 lead octet.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ previous code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">unsigned char</span>* w = twochars + <span class=
+"literal">3</span>;
+<span class="keyword">int</span> cp = previous (w, twochars - <span class=
+"literal">1</span>);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars);
+</pre>
+ <p>
+ <code>utf8::previous</code> is deprecated, and <code>utf8::prior</code> should
+ be used instead, although the existing code can continue using this function.
+ The problem is the parameter <code>pass_start</code> that points to the position
+ just before the beginning of the sequence. Standard containers don't have the
+ concept of "pass start" and the function can not be used with their iterators.
+ </p>
+ <p>
+ <code>it</code> will typically point to the beginning of
+ a code point, and <code>pass_start</code> will point to the octet just before the
+ beginning of the string to ensure we don't go backwards too far. <code>it</code> is
+ decreased until it points to a lead UTF-8 octet, and then the UTF-8 sequence
+ beginning with that octet is decoded to a 32 bit representation and returned.
+ </p>
+ <p>
+ In case <code>pass_start</code> is reached before a UTF-8 lead octet is hit, or if an
+ invalid UTF-8 sequence is started by the lead octet, an <code>invalid_utf8</code>
+ exception is thrown
+ </p>
+ <h4>
+ utf8::advance
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Advances an iterator by the specified number of code points within an UTF-8
+ sequence.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, typename distance_type&gt;
+<span class=
+"keyword">void</span> advance (octet_iterator&amp; it, distance_type n, octet_iterator end);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>distance_type</code>: an integral type convertible to <code>octet_iterator</code>'s difference type.<br>
+ <code>it</code>: a reference to an iterator pointing to the beginning of an UTF-8
+ encoded code point. After the function returns, it is incremented to point to the
+ nth following code point.<br>
+ <code>n</code>: a positive integer that shows how many code points we want to
+ advance.<br>
+ <code>end</code>: end of the UTF-8 sequence to be processed. If <code>it</code>
+ gets equal to <code>end</code> during the extraction of a code point, an
+ <code>utf8::not_enough_room</code> exception is thrown.<br>
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">unsigned char</span>* w = twochars;
+advance (w, <span class="literal">2</span>, twochars + <span class="literal">6</span>);
+assert (w == twochars + <span class="literal">5</span>);
+</pre>
+ <p>
+ This function works only "forward". In case of a negative <code>n</code>, there is
+ no effect.
+ </p>
+ <p>
+ In case of an invalid code point, a <code>utf8::invalid_code_point</code> exception
+ is thrown.
+ </p>
+ <h4>
+ utf8::distance
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Given the iterators to two UTF-8 encoded code points in a seqence, returns the
+ number of code points between them.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+<span class=
+"keyword">typename</span> std::iterator_traits&lt;octet_iterator&gt;::difference_type distance (octet_iterator first, octet_iterator last);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>first</code>: an iterator to a beginning of a UTF-8 encoded code point.<br>
+ <code>last</code>: an iterator to a "post-end" of the last UTF-8 encoded code
+ point in the sequence we are trying to determine the length. It can be the
+ beginning of a new code point, or not.<br>
+ <span class="return_value">Return value</span> the distance between the iterators,
+ in code points.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+size_t dist = utf8::distance(twochars, twochars + <span class="literal">5</span>);
+assert (dist == <span class="literal">2</span>);
+</pre>
+ <p>
+ This function is used to find the length (in code points) of a UTF-8 encoded
+ string. The reason it is called <em>distance</em>, rather than, say,
+ <em>length</em> is mainly because developers are used that <em>length</em> is an
+ O(1) function. Computing the length of an UTF-8 string is a linear operation, and
+ it looked better to model it after <code>std::distance</code> algorithm.
+ </p>
+ <p>
+ In case of an invalid UTF-8 seqence, a <code>utf8::invalid_utf8</code> exception is
+ thrown. If <code>last</code> does not point to the past-of-end of a UTF-8 seqence,
+ a <code>utf8::not_enough_room</code> exception is thrown.
+ </p>
+ <h4>
+ utf8::utf16to8
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts a UTF-16 encoded string to UTF-8.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> u16bit_iterator, <span class=
+"keyword">typename</span> octet_iterator&gt;
+octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result);
+
+</pre>
+ <p>
+ <code>u16bit_iterator</code>: an input iterator.<br>
+ <code>octet_iterator</code>: an output iterator.<br>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-16 encoded
+ string to convert.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-16 encoded
+ string to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-8 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-8 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">unsigned short</span> utf16string[] = {<span class=
+"literal">0x41</span>, <span class="literal">0x0448</span>, <span class=
+"literal">0x65e5</span>, <span class="literal">0xd834</span>, <span class=
+"literal">0xdd1e</span>};
+vector&lt;<span class="keyword">unsigned char</span>&gt; utf8result;
+utf16to8(utf16string, utf16string + <span class=
+"literal">5</span>, back_inserter(utf8result));
+assert (utf8result.size() == <span class="literal">10</span>);
+</pre>
+ <p>
+ In case of invalid UTF-16 sequence, a <code>utf8::invalid_utf16</code> exception is
+ thrown.
+ </p>
+ <h4>
+ utf8::utf8to16
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts an UTF-8 encoded string to UTF-16
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> u16bit_iterator, typename octet_iterator&gt;
+u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>u16bit_iterator</code>: an output iterator.<br>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-8 encoded
+ string to convert. &lt; br /&gt; <code>end</code>: an iterator pointing to
+ pass-the-end of the UTF-8 encoded string to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-16 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-16 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span> utf8_with_surrogates[] = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"</span>;
+vector &lt;<span class="keyword">unsigned short</span>&gt; utf16result;
+utf8to16(utf8_with_surrogates, utf8_with_surrogates + <span class=
+"literal">9</span>, back_inserter(utf16result));
+assert (utf16result.size() == <span class="literal">4</span>);
+assert (utf16result[<span class="literal">2</span>] == <span class=
+"literal">0xd834</span>);
+assert (utf16result[<span class="literal">3</span>] == <span class=
+"literal">0xdd1e</span>);
+</pre>
+ <p>
+ In case of an invalid UTF-8 seqence, a <code>utf8::invalid_utf8</code> exception is
+ thrown. If <code>end</code> does not point to the past-of-end of a UTF-8 seqence, a
+ <code>utf8::not_enough_room</code> exception is thrown.
+ </p>
+ <h4>
+ utf8::utf32to8
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts a UTF-32 encoded string to UTF-8.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, typename u32bit_iterator&gt;
+octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an output iterator.<br>
+ <code>u32bit_iterator</code>: an input iterator.<br>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-32 encoded
+ string to convert.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-32 encoded
+ string to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-8 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-8 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">int</span> utf32string[] = {<span class=
+"literal">0x448</span>, <span class="literal">0x65E5</span>, <span class=
+"literal">0x10346</span>, <span class="literal">0</span>};
+vector&lt;<span class="keyword">unsigned char</span>&gt; utf8result;
+utf32to8(utf32string, utf32string + <span class=
+"literal">3</span>, back_inserter(utf8result));
+assert (utf8result.size() == <span class="literal">9</span>);
+</pre>
+ <p>
+ In case of invalid UTF-32 string, a <code>utf8::invalid_code_point</code> exception
+ is thrown.
+ </p>
+ <h4>
+ utf8::utf8to32
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts a UTF-8 encoded string to UTF-32.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, <span class=
+"keyword">typename</span> u32bit_iterator&gt;
+u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>u32bit_iterator</code>: an output iterator.<br>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-8 encoded
+ string to convert.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-8 encoded string
+ to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-32 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-32 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+vector&lt;<span class="keyword">int</span>&gt; utf32result;
+utf8to32(twochars, twochars + <span class=
+"literal">5</span>, back_inserter(utf32result));
+assert (utf32result.size() == <span class="literal">2</span>);
+</pre>
+ <p>
+ In case of an invalid UTF-8 seqence, a <code>utf8::invalid_utf8</code> exception is
+ thrown. If <code>end</code> does not point to the past-of-end of a UTF-8 seqence, a
+ <code>utf8::not_enough_room</code> exception is thrown.
+ </p>
+ <h4>
+ utf8::find_invalid
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Detects an invalid sequence within a UTF-8 string.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+octet_iterator find_invalid(octet_iterator start, octet_iterator end);
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-8 string to
+ test for validity.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-8 string to test
+ for validity.<br>
+ <span class="return_value">Return value</span>: an iterator pointing to the first
+ invalid octet in the UTF-8 string. In case none were found, equals
+ <code>end</code>.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span> utf_invalid[] = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88\xfa"</span>;
+<span class=
+"keyword">char</span>* invalid = find_invalid(utf_invalid, utf_invalid + <span class=
+"literal">6</span>);
+assert (invalid == utf_invalid + <span class="literal">5</span>);
+</pre>
+ <p>
+ This function is typically used to make sure a UTF-8 string is valid before
+ processing it with other functions. It is especially important to call it if before
+ doing any of the <em>unchecked</em> operations on it.
+ </p>
+ <h4>
+ utf8::is_valid
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Checks whether a sequence of octets is a valid UTF-8 string.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+<span class="keyword">bool</span> is_valid(octet_iterator start, octet_iterator end);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-8 string to
+ test for validity.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-8 string to test
+ for validity.<br>
+ <span class="return_value">Return value</span>: <code>true</code> if the sequence
+ is a valid UTF-8 string; <code>false</code> if not.
+ </p>
+ Example of use:
+<pre>
+<span class="keyword">char</span> utf_invalid[] = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88\xfa"</span>;
+<span class="keyword">bool</span> bvalid = is_valid(utf_invalid, utf_invalid + <span
+class="literal">6</span>);
+assert (bvalid == false);
+</pre>
+ <p>
+ <code>is_valid</code> is a shorthand for <code>find_invalid(start, end) ==
+ end;</code>. You may want to use it to make sure that a byte seqence is a valid
+ UTF-8 string without the need to know where it fails if it is not valid.
+ </p>
+ <h4>
+ utf8::replace_invalid
+ </h4>
+ <p class="version">
+ Available in version 2.0 and later.
+ </p>
+ <p>
+ Replaces all invalid UTF-8 sequences within a string with a replacement marker.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, <span class=
+"keyword">typename</span> output_iterator&gt;
+output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement);
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, <span class=
+"keyword">typename</span> output_iterator&gt;
+output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out);
+
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>output_iterator</code>: an output iterator.<br>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-8 string to
+ look for invalid UTF-8 sequences.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-8 string to look
+ for invalid UTF-8 sequences.<br>
+ <code>out</code>: An output iterator to the range where the result of replacement
+ is stored.<br>
+ <code>replacement</code>: A Unicode code point for the replacement marker. The
+ version without this parameter assumes the value <code>0xfffd</code><br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the UTF-8 string with replaced invalid sequences.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span> invalid_sequence[] = <span class=
+"literal">"a\x80\xe0\xa0\xc0\xaf\xed\xa0\x80z"</span>;
+vector&lt;<span class="keyword">char</span>&gt; replace_invalid_result;
+replace_invalid (invalid_sequence, invalid_sequence + sizeof(invalid_sequence), back_inserter(replace_invalid_result), <span
+ class="literal">'?'</span>);
+bvalid = is_valid(replace_invalid_result.begin(), replace_invalid_result.end());
+assert (bvalid);
+<span class="keyword">char</span>* fixed_invalid_sequence = <span class=
+"literal">"a????z"</span>;
+assert (std::equal(replace_invalid_result.begin(), replace_invalid_result.end(), fixed_invalid_sequence));
+</pre>
+ <p>
+ <code>replace_invalid</code> does not perform in-place replacement of invalid
+ sequences. Rather, it produces a copy of the original string with the invalid
+ sequences replaced with a replacement marker. Therefore, <code>out</code> must not
+ be in the <code>[start, end]</code> range.
+ </p>
+ <p>
+ If <code>end</code> does not point to the past-of-end of a UTF-8 sequence, a
+ <code>utf8::not_enough_room</code> exception is thrown.
+ </p>
+ <h4>
+ utf8::starts_with_bom
+ </h4>
+ <p class="version">
+ Available in version 2.3 and later. Relaces deprecated <code>is_bom()</code> function.
+ </p>
+ <p>
+ Checks whether an octet sequence starts with a UTF-8 byte order mark (BOM)
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+<span class="keyword">bool</span> starts_with_bom (octet_iterator it, octet_iterator end);
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>it</code>: beginning of the octet sequence to check<br>
+ <code>end</code>: pass-end of the sequence to check<br>
+ <span class="return_value">Return value</span>: <code>true</code> if the sequence
+ starts with a UTF-8 byte order mark; <code>false</code> if not.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">unsigned char</span> byte_order_mark[] = {<span class=
+"literal">0xef</span>, <span class="literal">0xbb</span>, <span class=
+"literal">0xbf</span>};
+<span class="keyword">bool</span> bbom = starts_with_bom(byte_order_mark, byte_order_mark + <span class="keyword">sizeof</span>(byte_order_mark));
+assert (bbom == <span class="literal">true</span>);
+</pre>
+ <p>
+ The typical use of this function is to check the first three bytes of a file. If
+ they form the UTF-8 BOM, we want to skip them before processing the actual UTF-8
+ encoded text.
+ </p>
+ <h4>
+ utf8::is_bom
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later. Deprecated in version 2.3. <code>starts_with_bom()</code> should be used
+ instead.
+ </p>
+ <p>
+ Checks whether a sequence of three octets is a UTF-8 byte order mark (BOM)
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+<span class="keyword">bool</span> is_bom (octet_iterator it); <span class="comment"> // Deprecated</span>
+</pre>
+ <p>
+ <code>octet_iterator</code>: an input iterator.<br>
+ <code>it</code>: beginning of the 3-octet sequence to check<br>
+ <span class="return_value">Return value</span>: <code>true</code> if the sequence
+ is UTF-8 byte order mark; <code>false</code> if not.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">unsigned char</span> byte_order_mark[] = {<span class=
+"literal">0xef</span>, <span class="literal">0xbb</span>, <span class=
+"literal">0xbf</span>};
+<span class="keyword">bool</span> bbom = is_bom(byte_order_mark);
+assert (bbom == <span class="literal">true</span>);
+</pre>
+ <p>
+ The typical use of this function is to check the first three bytes of a file. If
+ they form the UTF-8 BOM, we want to skip them before processing the actual UTF-8
+ encoded text.
+ </p>
+ <p>
+ If a sequence is
+ shorter than three bytes, an invalid iterator will be dereferenced. Therefore, this function is deprecated
+ in favor of <code>starts_with_bom()</code>that takes the end of sequence as an argument.
+ </p>
+ <h3 id="typesutf8">
+ Types From utf8 Namespace
+ </h3>
+ <h4>utf8::exception
+ </h4>
+ <p class="version">
+ Available in version 2.3 and later.
+ </p>
+ <p>
+ Base class for the exceptions thrown by UTF CPP library functions.
+ </p>
+<pre>
+<span class="keyword">class</span> exception : <span class="keyword">public</span> std::exception {};
+</pre>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">try</span> {
+ code_that_uses_utf_cpp_library();
+}
+<span class="keyword">catch</span>(<span class="keyword">const</span> utf8::exception&amp; utfcpp_ex) {
+ cerr &lt;&lt; utfcpp_ex.what();
+}
+</pre>
+
+ <h4>utf8::invalid_code_point
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Thrown by UTF8 CPP functions such as <code>advance</code> and <code>next</code> if an UTF-8 sequence represents and invalid code point.
+ </p>
+
+<pre>
+<span class="keyword">class</span> invalid_code_point : <span class="keyword">public</span> exception {
+<span class="keyword">public</span>:
+ uint32_t code_point() <span class="keyword">const</span>;
+};
+
+</pre>
+ <p>
+ Member function <code>code_point()</code> can be used to determine the invalid code point that
+ caused the exception to be thrown.
+ </p>
+ <h4>utf8::invalid_utf8
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Thrown by UTF8 CPP functions such as <code>next</code> and <code>prior</code> if an invalid UTF-8 sequence
+ is detected during decoding.
+ </p>
+
+<pre>
+<span class="keyword">class</span> invalid_utf8 : <span class="keyword">public</span> exception {
+<span class="keyword">public</span>:
+ uint8_t utf8_octet() <span class="keyword">const</span>;
+};
+</pre>
+
+ <p>
+ Member function <code>utf8_octet()</code> can be used to determine the beginning of the byte
+ sequence that caused the exception to be thrown.
+ </p>
+</pre>
+ <h4>utf8::invalid_utf16
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Thrown by UTF8 CPP function <code>utf16to8</code> if an invalid UTF-16 sequence
+ is detected during decoding.
+ </p>
+
+<pre>
+<span class="keyword">class</span> invalid_utf16 : <span class="keyword">public</span> exception {
+<span class="keyword">public</span>:
+ uint16_t utf16_word() <span class="keyword">const</span>;
+};
+</pre>
+
+ <p>
+ Member function <code>utf16_word()</code> can be used to determine the UTF-16 code unit
+ that caused the exception to be thrown.
+ </p>
+ <h4>utf8::not_enough_room
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Thrown by UTF8 CPP functions such as <code>next</code> if the end of the decoded UTF-8 sequence
+ was reached before the code point was decoded.
+ </p>
+
+<pre>
+<span class="keyword">class</span> not_enough_room : <span class="keyword">public</span> exception {};
+</pre>
+ <h4>
+ utf8::iterator
+ </h4>
+ <p class="version">
+ Available in version 2.0 and later.
+ </p>
+ <p>
+ Adapts the underlying octet iterator to iterate over the sequence of code points,
+ rather than raw octets.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class="keyword">typename</span> octet_iterator&gt;
+<span class="keyword">class</span> iterator;
+</pre>
+
+ <h5>Member functions</h5>
+ <dl>
+ <dt><code>iterator();</code> <dd> the deafult constructor; the underlying <code>octet_iterator</code> is
+ constructed with its default constructor.
+ <dt><code><span class="keyword">explicit</span> iterator (const octet_iterator&amp; octet_it,
+ const octet_iterator&amp; range_start,
+ const octet_iterator&amp; range_end);</code> <dd> a constructor
+ that initializes the underlying <code>octet_iterator</code> with <code>octet_it</code>
+ and sets the range in which the iterator is considered valid.
+ <dt><code>octet_iterator base () <span class="keyword">const</span>;</code> <dd> returns the
+ underlying <code>octet_iterator</code>.
+ <dt><code>uint32_t operator * () <span class="keyword">const</span>;</code> <dd> decodes the utf-8 sequence
+ the underlying <code>octet_iterator</code> is pointing to and returns the code point.
+ <dt><code><span class="keyword">bool operator</span> == (const iterator&amp; rhs)
+ <span class="keyword">const</span>;</code> <dd> returns <span class="keyword">true</span>
+ if the two underlaying iterators are equal.
+ <dt><code><span class="keyword">bool operator</span> != (const iterator&amp; rhs)
+ <span class="keyword">const</span>;</code> <dd> returns <span class="keyword">true</span>
+ if the two underlaying iterators are not equal.
+ <dt><code>iterator&amp; <span class="keyword">operator</span> ++ (); </code> <dd> the prefix increment - moves
+ the iterator to the next UTF-8 encoded code point.
+ <dt><code>iterator <span class="keyword">operator</span> ++ (<span class="keyword">int</span>); </code> <dd>
+ the postfix increment - moves the iterator to the next UTF-8 encoded code point and returns the current one.
+ <dt><code>iterator&amp; <span class="keyword">operator</span> -- (); </code> <dd> the prefix decrement - moves
+ the iterator to the previous UTF-8 encoded code point.
+ <dt><code>iterator <span class="keyword">operator</span> -- (<span class="keyword">int</span>); </code> <dd>
+ the postfix decrement - moves the iterator to the previous UTF-8 encoded code point and returns the current one.
+ </dl>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* threechars = <span class="literal">"\xf0\x90\x8d\x86\xe6\x97\xa5\xd1\x88"</span>;
+utf8::iterator&lt;<span class="keyword">char</span>*&gt; it(threechars, threechars, threechars + <span class="literal">9</span>);
+utf8::iterator&lt;<span class="keyword">char</span>*&gt; it2 = it;
+assert (it2 == it);
+assert (*it == <span class="literal">0x10346</span>);
+assert (*(++it) == <span class="literal">0x65e5</span>);
+assert ((*it++) == <span class="literal">0x65e5</span>);
+assert (*it == <span class="literal">0x0448</span>);
+assert (it != it2);
+utf8::iterator&lt;<span class="keyword">char</span>*&gt; endit (threechars + <span class="literal">9</span>, threechars, threechars + <span class="literal">9</span>);
+assert (++it == endit);
+assert (*(--it) == <span class="literal">0x0448</span>);
+assert ((*it--) == <span class="literal">0x0448</span>);
+assert (*it == <span class="literal">0x65e5</span>);
+assert (--it == utf8::iterator&lt;<span class="keyword">char</span>*&gt;(threechars, threechars, threechars + <span class="literal">9</span>));
+assert (*it == <span class="literal">0x10346</span>);
+</pre>
+ <p>
+ The purpose of <code>utf8::iterator</code> adapter is to enable easy iteration as well as the use of STL
+ algorithms with UTF-8 encoded strings. Increment and decrement operators are implemented in terms of
+ <code>utf8::next()</code> and <code>utf8::prior()</code> functions.
+ </p>
+ <p>
+ Note that <code>utf8::iterator</code> adapter is a checked iterator. It operates on the range specified in
+ the constructor; any attempt to go out of that range will result in an exception. Even the comparison operators
+ require both iterator object to be constructed against the same range - otherwise an exception is thrown. Typically,
+ the range will be determined by sequence container functions <code>begin</code> and <code>end</code>, i.e.:
+ </p>
+<pre>
+std::string s = <span class="literal">"example"</span>;
+utf8::iterator i (s.begin(), s.begin(), s.end());
+</pre>
+ <h3 id="fununchecked">
+ Functions From utf8::unchecked Namespace
+ </h3>
+ <h4>
+ utf8::unchecked::append
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Encodes a 32 bit code point as a UTF-8 sequence of octets and appends the sequence
+ to a UTF-8 string.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+octet_iterator append(uint32_t cp, octet_iterator result);
+
+</pre>
+ <p>
+ <code>cp</code>: A 32 bit integer representing a code point to append to the
+ sequence.<br>
+ <code>result</code>: An output iterator to the place in the sequence where to
+ append the code point.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the newly appended sequence.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">unsigned char</span> u[<span class="literal">5</span>] = {<span
+class="literal">0</span>,<span class="literal">0</span>,<span class=
+"literal">0</span>,<span class="literal">0</span>,<span class="literal">0</span>};
+<span class="keyword">unsigned char</span>* end = unchecked::append(<span class=
+"literal">0x0448</span>, u);
+assert (u[<span class="literal">0</span>] == <span class=
+"literal">0xd1</span> &amp;&amp; u[<span class="literal">1</span>] == <span class=
+"literal">0x88</span> &amp;&amp; u[<span class="literal">2</span>] == <span class=
+"literal">0</span> &amp;&amp; u[<span class="literal">3</span>] == <span class=
+"literal">0</span> &amp;&amp; u[<span class="literal">4</span>] == <span class=
+"literal">0</span>);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::append</code>. It does not
+ check for validity of the supplied code point, and may produce an invalid UTF-8
+ sequence.
+ </p>
+ <h4>
+ utf8::unchecked::next
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Given the iterator to the beginning of a UTF-8 sequence, it returns the code point
+ and moves the iterator to the next position.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t next(octet_iterator&amp; it);
+
+</pre>
+ <p>
+ <code>it</code>: a reference to an iterator pointing to the beginning of an UTF-8
+ encoded code point. After the function returns, it is incremented to point to the
+ beginning of the next code point.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ processed UTF-8 code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">char</span>* w = twochars;
+<span class="keyword">int</span> cp = unchecked::next(w);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars + <span class="literal">3</span>);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::next</code>. It does not
+ check for validity of the supplied UTF-8 sequence.
+ </p>
+ <h4>
+ utf8::unchecked::peek_next
+ </h4>
+ <p class="version">
+ Available in version 2.1 and later.
+ </p>
+ <p>
+ Given the iterator to the beginning of a UTF-8 sequence, it returns the code point.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t peek_next(octet_iterator it);
+
+</pre>
+ <p>
+ <code>it</code>: an iterator pointing to the beginning of an UTF-8
+ encoded code point.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ processed UTF-8 code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">char</span>* w = twochars;
+<span class="keyword">int</span> cp = unchecked::peek_next(w);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::peek_next</code>. It does not
+ check for validity of the supplied UTF-8 sequence.
+ </p>
+ <h4>
+ utf8::unchecked::prior
+ </h4>
+ <p class="version">
+ Available in version 1.02 and later.
+ </p>
+ <p>
+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it
+ decreases the iterator until it hits the beginning of the previous UTF-8 encoded
+ code point and returns the 32 bits representation of the code point.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t prior(octet_iterator&amp; it);
+
+</pre>
+ <p>
+ <code>it</code>: a reference pointing to an octet within a UTF-8 encoded string.
+ After the function returns, it is decremented to point to the beginning of the
+ previous code point.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ previous code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">char</span>* w = twochars + <span class="literal">3</span>;
+<span class="keyword">int</span> cp = unchecked::prior (w);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::prior</code>. It does not
+ check for validity of the supplied UTF-8 sequence and offers no boundary checking.
+ </p>
+ <h4>
+ utf8::unchecked::previous (deprecated, see utf8::unchecked::prior)
+ </h4>
+ <p class="version">
+ Deprecated in version 1.02 and later.
+ </p>
+ <p>
+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it
+ decreases the iterator until it hits the beginning of the previous UTF-8 encoded
+ code point and returns the 32 bits representation of the code point.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+uint32_t previous(octet_iterator&amp; it);
+
+</pre>
+ <p>
+ <code>it</code>: a reference pointing to an octet within a UTF-8 encoded string.
+ After the function returns, it is decremented to point to the beginning of the
+ previous code point.<br>
+ <span class="return_value">Return value</span>: the 32 bit representation of the
+ previous code point.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">char</span>* w = twochars + <span class="literal">3</span>;
+<span class="keyword">int</span> cp = unchecked::previous (w);
+assert (cp == <span class="literal">0x65e5</span>);
+assert (w == twochars);
+</pre>
+ <p>
+ The reason this function is deprecated is just the consistency with the "checked"
+ versions, where <code>prior</code> should be used instead of <code>previous</code>.
+ In fact, <code>unchecked::previous</code> behaves exactly the same as <code>
+ unchecked::prior</code>
+ </p>
+ <p>
+ This is a faster but less safe version of <code>utf8::previous</code>. It does not
+ check for validity of the supplied UTF-8 sequence and offers no boundary checking.
+ </p>
+ <h4>
+ utf8::unchecked::advance
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Advances an iterator by the specified number of code points within an UTF-8
+ sequence.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, typename distance_type&gt;
+<span class="keyword">void</span> advance (octet_iterator&amp; it, distance_type n);
+
+</pre>
+ <p>
+ <code>it</code>: a reference to an iterator pointing to the beginning of an UTF-8
+ encoded code point. After the function returns, it is incremented to point to the
+ nth following code point.<br>
+ <code>n</code>: a positive integer that shows how many code points we want to
+ advance.<br>
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+<span class="keyword">char</span>* w = twochars;
+unchecked::advance (w, <span class="literal">2</span>);
+assert (w == twochars + <span class="literal">5</span>);
+</pre>
+ <p>
+ This function works only "forward". In case of a negative <code>n</code>, there is
+ no effect.
+ </p>
+ <p>
+ This is a faster but less safe version of <code>utf8::advance</code>. It does not
+ check for validity of the supplied UTF-8 sequence and offers no boundary checking.
+ </p>
+ <h4>
+ utf8::unchecked::distance
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Given the iterators to two UTF-8 encoded code points in a seqence, returns the
+ number of code points between them.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator&gt;
+<span class=
+"keyword">typename</span> std::iterator_traits&lt;octet_iterator&gt;::difference_type distance (octet_iterator first, octet_iterator last);
+</pre>
+ <p>
+ <code>first</code>: an iterator to a beginning of a UTF-8 encoded code point.<br>
+ <code>last</code>: an iterator to a "post-end" of the last UTF-8 encoded code
+ point in the sequence we are trying to determine the length. It can be the
+ beginning of a new code point, or not.<br>
+ <span class="return_value">Return value</span> the distance between the iterators,
+ in code points.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+size_t dist = utf8::unchecked::distance(twochars, twochars + <span class=
+"literal">5</span>);
+assert (dist == <span class="literal">2</span>);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::distance</code>. It does not
+ check for validity of the supplied UTF-8 sequence.
+ </p>
+ <h4>
+ utf8::unchecked::utf16to8
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts a UTF-16 encoded string to UTF-8.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> u16bit_iterator, <span class=
+"keyword">typename</span> octet_iterator&gt;
+octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result);
+
+</pre>
+ <p>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-16 encoded
+ string to convert.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-16 encoded
+ string to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-8 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-8 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">unsigned short</span> utf16string[] = {<span class=
+"literal">0x41</span>, <span class="literal">0x0448</span>, <span class=
+"literal">0x65e5</span>, <span class="literal">0xd834</span>, <span class=
+"literal">0xdd1e</span>};
+vector&lt;<span class="keyword">unsigned char</span>&gt; utf8result;
+unchecked::utf16to8(utf16string, utf16string + <span class=
+"literal">5</span>, back_inserter(utf8result));
+assert (utf8result.size() == <span class="literal">10</span>);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::utf16to8</code>. It does not
+ check for validity of the supplied UTF-16 sequence.
+ </p>
+ <h4>
+ utf8::unchecked::utf8to16
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts an UTF-8 encoded string to UTF-16
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> u16bit_iterator, typename octet_iterator&gt;
+u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result);
+
+</pre>
+ <p>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-8 encoded
+ string to convert. &lt; br /&gt; <code>end</code>: an iterator pointing to
+ pass-the-end of the UTF-8 encoded string to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-16 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-16 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span> utf8_with_surrogates[] = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"</span>;
+vector &lt;<span class="keyword">unsigned short</span>&gt; utf16result;
+unchecked::utf8to16(utf8_with_surrogates, utf8_with_surrogates + <span class=
+"literal">9</span>, back_inserter(utf16result));
+assert (utf16result.size() == <span class="literal">4</span>);
+assert (utf16result[<span class="literal">2</span>] == <span class=
+"literal">0xd834</span>);
+assert (utf16result[<span class="literal">3</span>] == <span class=
+"literal">0xdd1e</span>);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::utf8to16</code>. It does not
+ check for validity of the supplied UTF-8 sequence.
+ </p>
+ <h4>
+ utf8::unchecked::utf32to8
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts a UTF-32 encoded string to UTF-8.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, <span class=
+"keyword">typename</span> u32bit_iterator&gt;
+octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result);
+
+</pre>
+ <p>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-32 encoded
+ string to convert.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-32 encoded
+ string to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-8 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-8 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">int</span> utf32string[] = {<span class=
+"literal">0x448</span>, <span class="literal">0x65e5</span>, <span class=
+"literal">0x10346</span>, <span class="literal">0</span>};
+vector&lt;<span class="keyword">unsigned char</span>&gt; utf8result;
+utf32to8(utf32string, utf32string + <span class=
+"literal">3</span>, back_inserter(utf8result));
+assert (utf8result.size() == <span class="literal">9</span>);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::utf32to8</code>. It does not
+ check for validity of the supplied UTF-32 sequence.
+ </p>
+ <h4>
+ utf8::unchecked::utf8to32
+ </h4>
+ <p class="version">
+ Available in version 1.0 and later.
+ </p>
+ <p>
+ Converts a UTF-8 encoded string to UTF-32.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class=
+"keyword">typename</span> octet_iterator, typename u32bit_iterator&gt;
+u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result);
+
+</pre>
+ <p>
+ <code>start</code>: an iterator pointing to the beginning of the UTF-8 encoded
+ string to convert.<br>
+ <code>end</code>: an iterator pointing to pass-the-end of the UTF-8 encoded string
+ to convert.<br>
+ <code>result</code>: an output iterator to the place in the UTF-32 string where to
+ append the result of conversion.<br>
+ <span class="return_value">Return value</span>: An iterator pointing to the place
+ after the appended UTF-32 string.
+ </p>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* twochars = <span class=
+"literal">"\xe6\x97\xa5\xd1\x88"</span>;
+vector&lt;<span class="keyword">int</span>&gt; utf32result;
+unchecked::utf8to32(twochars, twochars + <span class=
+"literal">5</span>, back_inserter(utf32result));
+assert (utf32result.size() == <span class="literal">2</span>);
+</pre>
+ <p>
+ This is a faster but less safe version of <code>utf8::utf8to32</code>. It does not
+ check for validity of the supplied UTF-8 sequence.
+ </p>
+ <h3 id="typesunchecked">
+ Types From utf8::unchecked Namespace
+ </h3>
+ <h4>
+ utf8::iterator
+ </h4>
+ <p class="version">
+ Available in version 2.0 and later.
+ </p>
+ <p>
+ Adapts the underlying octet iterator to iterate over the sequence of code points,
+ rather than raw octets.
+ </p>
+<pre>
+<span class="keyword">template</span> &lt;<span class="keyword">typename</span> octet_iterator&gt;
+<span class="keyword">class</span> iterator;
+</pre>
+
+ <h5>Member functions</h5>
+ <dl>
+ <dt><code>iterator();</code> <dd> the deafult constructor; the underlying <code>octet_iterator</code> is
+ constructed with its default constructor.
+ <dt><code><span class="keyword">explicit</span> iterator (const octet_iterator&amp; octet_it);
+ </code> <dd> a constructor
+ that initializes the underlying <code>octet_iterator</code> with <code>octet_it</code>
+ <dt><code>octet_iterator base () <span class="keyword">const</span>;</code> <dd> returns the
+ underlying <code>octet_iterator</code>.
+ <dt><code>uint32_t operator * () <span class="keyword">const</span>;</code> <dd> decodes the utf-8 sequence
+ the underlying <code>octet_iterator</code> is pointing to and returns the code point.
+ <dt><code><span class="keyword">bool operator</span> == (const iterator&amp; rhs)
+ <span class="keyword">const</span>;</code> <dd> returns <span class="keyword">true</span>
+ if the two underlaying iterators are equal.
+ <dt><code><span class="keyword">bool operator</span> != (const iterator&amp; rhs)
+ <span class="keyword">const</span>;</code> <dd> returns <span class="keyword">true</span>
+ if the two underlaying iterators are not equal.
+ <dt><code>iterator&amp; <span class="keyword">operator</span> ++ (); </code> <dd> the prefix increment - moves
+ the iterator to the next UTF-8 encoded code point.
+ <dt><code>iterator <span class="keyword">operator</span> ++ (<span class="keyword">int</span>); </code> <dd>
+ the postfix increment - moves the iterator to the next UTF-8 encoded code point and returns the current one.
+ <dt><code>iterator&amp; <span class="keyword">operator</span> -- (); </code> <dd> the prefix decrement - moves
+ the iterator to the previous UTF-8 encoded code point.
+ <dt><code>iterator <span class="keyword">operator</span> -- (<span class="keyword">int</span>); </code> <dd>
+ the postfix decrement - moves the iterator to the previous UTF-8 encoded code point and returns the current one.
+ </dl>
+ <p>
+ Example of use:
+ </p>
+<pre>
+<span class="keyword">char</span>* threechars = <span class="literal">"\xf0\x90\x8d\x86\xe6\x97\xa5\xd1\x88"</span>;
+utf8::unchecked::iterator&lt;<span class="keyword">char</span>*&gt; un_it(threechars);
+utf8::unchecked::iterator&lt;<span class="keyword">char</span>*&gt; un_it2 = un_it;
+assert (un_it2 == un_it);
+assert (*un_it == <span class="literal">0x10346</span>);
+assert (*(++un_it) == <span class="literal">0x65e5</span>);
+assert ((*un_it++) == <span class="literal">0x65e5</span>);
+assert (*un_it == <span class="literal">0x0448</span>);
+assert (un_it != un_it2);
+utf8::::unchecked::iterator&lt;<span class="keyword">char</span>*&gt; un_endit (threechars + <span class="literal">9</span>);
+assert (++un_it == un_endit);
+assert (*(--un_it) == <span class="literal">0x0448</span>);
+assert ((*un_it--) == <span class="literal">0x0448</span>);
+assert (*un_it == <span class="literal">0x65e5</span>);
+assert (--un_it == utf8::unchecked::iterator&lt;<span class="keyword">char</span>*&gt;(threechars));
+assert (*un_it == <span class="literal">0x10346</span>);
+</pre>
+ <p>
+ This is an unchecked version of <code>utf8::iterator</code>. It is faster in many cases, but offers
+ no validity or range checks.
+ </p>
+ <h2 id="points">
+ Points of interest
+ </h2>
+ <h4>
+ Design goals and decisions
+ </h4>
+ <p>
+ The library was designed to be:
+ </p>
+ <ol>
+ <li>
+ Generic: for better or worse, there are many C++ string classes out there, and
+ the library should work with as many of them as possible.
+ </li>
+ <li>
+ Portable: the library should be portable both accross different platforms and
+ compilers. The only non-portable code is a small section that declares unsigned
+ integers of different sizes: three typedefs. They can be changed by the users of
+ the library if they don't match their platform. The default setting should work
+ for Windows (both 32 and 64 bit), and most 32 bit and 64 bit Unix derivatives.
+ </li>
+ <li>
+ Lightweight: follow the "pay only for what you use" guideline.
+ </li>
+ <li>
+ Unintrusive: avoid forcing any particular design or even programming style on the
+ user. This is a library, not a framework.
+ </li>
+ </ol>
+ <h4>
+ Alternatives
+ </h4>
+ <p>
+ In case you want to look into other means of working with UTF-8 strings from C++,
+ here is the list of solutions I am aware of:
+ </p>
+ <ol>
+ <li>
+ <a href="http://icu.sourceforge.net/">ICU Library</a>. It is very powerful,
+ complete, feature-rich, mature, and widely used. Also big, intrusive,
+ non-generic, and doesn't play well with the Standard Library. I definitelly
+ recommend looking at ICU even if you don't plan to use it.
+ </li>
+ <li>
+ C++11 language and library features. Still far from complete, and not widely
+ supported by compiler vendors.
+ </li>
+ <li>
+ <a href=
+ "http://www.gtkmm.org/gtkmm2/docs/tutorial/html/ch03s04.html">Glib::ustring</a>.
+ A class specifically made to work with UTF-8 strings, and also feel like
+ <code>std::string</code>. If you prefer to have yet another string class in your
+ code, it may be worth a look. Be aware of the licensing issues, though.
+ </li>
+ <li>
+ Platform dependent solutions: Windows and POSIX have functions to convert strings
+ from one encoding to another. That is only a subset of what my library offers,
+ but if that is all you need it may be good enough.
+ </li>
+ </ol>
+ <h2 id="links">
+ Links
+ </h2>
+ <ol>
+ <li>
+ <a href="http://www.unicode.org/">The Unicode Consortium</a>.
+ </li>
+ <li>
+ <a href="http://icu.sourceforge.net/">ICU Library</a>.
+ </li>
+ <li>
+ <a href="http://en.wikipedia.org/wiki/UTF-8">UTF-8 at Wikipedia</a>
+ </li>
+ <li>
+ <a href="http://www.cl.cam.ac.uk/~mgk25/unicode.html">UTF-8 and Unicode FAQ for
+ Unix/Linux</a>
+ </li>
+ </ol>
+ </body>
+</html>
diff --git a/thirdparty/assimp/include/assimp/.editorconfig b/thirdparty/assimp/include/assimp/.editorconfig
deleted file mode 100644
index 9ea66423ad..0000000000
--- a/thirdparty/assimp/include/assimp/.editorconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-# See <http://EditorConfig.org> for details
-
-[*.{h,hpp,inl}]
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
-indent_size = 4
-indent_style = space
diff --git a/thirdparty/assimp/include/assimp/BaseImporter.h b/thirdparty/assimp/include/assimp/BaseImporter.h
index 48dfc8ed8b..55f7fe3754 100644
--- a/thirdparty/assimp/include/assimp/BaseImporter.h
+++ b/thirdparty/assimp/include/assimp/BaseImporter.h
@@ -48,8 +48,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <vector>
#include <set>
+#include <map>
#include <assimp/types.h>
#include <assimp/ProgressHandler.hpp>
+#include <assimp/ai_assert.h>
struct aiScene;
struct aiImporterDesc;
@@ -80,6 +82,10 @@ class IOStream;
class ASSIMP_API BaseImporter {
friend class Importer;
+private:
+ /* Pushes state into importer for the importer scale */
+ virtual void UpdateImporterScale( Importer* pImp );
+
public:
/** Constructor to be privately used by #Importer */
@@ -132,7 +138,7 @@ public:
* a suitable response to the caller.
*/
aiScene* ReadFile(
- const Importer* pImp,
+ Importer* pImp,
const std::string& pFile,
IOSystem* pIOHandler
);
@@ -161,14 +167,65 @@ public:
* some loader features. Importers must provide this information. */
virtual const aiImporterDesc* GetInfo() const = 0;
+ /**
+ * Will be called only by scale process when scaling is requested.
+ */
+ virtual void SetFileScale(double scale)
+ {
+ fileScale = scale;
+ }
+
+ virtual double GetFileScale() const
+ {
+ return fileScale;
+ }
+
+ enum ImporterUnits {
+ M,
+ MM,
+ CM,
+ INCHES,
+ FEET
+ };
+
+ /**
+ * Assimp Importer
+ * unit conversions available
+ * if you need another measurment unit add it below.
+ * it's currently defined in assimp that we prefer meters.
+ * */
+ std::map<ImporterUnits, double> importerUnits = {
+ {ImporterUnits::M, 1},
+ {ImporterUnits::CM, 0.01},
+ {ImporterUnits::MM, 0.001},
+ {ImporterUnits::INCHES, 0.0254},
+ {ImporterUnits::FEET, 0.3048}
+ };
+
+ virtual void SetApplicationUnits( const ImporterUnits& unit )
+ {
+ importerScale = importerUnits[unit];
+ applicationUnits = unit;
+ }
+
+ virtual const ImporterUnits& GetApplicationUnits()
+ {
+ return applicationUnits;
+ }
+
// -------------------------------------------------------------------
/** Called by #Importer::GetExtensionList for each loaded importer.
* Take the extension list contained in the structure returned by
* #GetInfo and insert all file extensions into the given set.
* @param extension set to collect file extensions in*/
void GetExtensionList(std::set<std::string>& extensions);
+
+protected:
+ ImporterUnits applicationUnits = ImporterUnits::M;
+ double importerScale = 1.0;
+ double fileScale = 1.0;
+
-protected:
// -------------------------------------------------------------------
/** Imports the given file into the given scene structure. The
diff --git a/thirdparty/assimp/include/assimp/config.h.in b/thirdparty/assimp/include/assimp/config.h.in
index d08b929a10..3a6379bf40 100644
--- a/thirdparty/assimp/include/assimp/config.h.in
+++ b/thirdparty/assimp/include/assimp/config.h.in
@@ -142,7 +142,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/** @brief Specifies the maximum angle that may be between two vertex tangents
* that their tangents and bi-tangents are smoothed.
*
- * This applies to the CalcTangentSpace-Step. TFvhe angle is specified
+ * This applies to the CalcTangentSpace-Step. The angle is specified
* in degrees. The maximum value is 175.
* Property type: float. Default value: 45 degrees
*/
@@ -999,6 +999,13 @@ enum aiComponent
# define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f
#endif // !! AI_DEBONE_THRESHOLD
+#define AI_CONFIG_APP_SCALE_KEY "APP_SCALE_FACTOR"
+
+#if (!defined AI_CONFIG_APP_SCALE_KEY)
+# define AI_CONFIG_APP_SCALE_KEY 1.0
+#endif // AI_CONFIG_APP_SCALE_KEY
+
+
// ---------- All the Build/Compile-time defines ------------
/** @brief Specifies if double precision is supported inside assimp
diff --git a/thirdparty/assimp/include/assimp/irrXMLWrapper.h b/thirdparty/assimp/include/assimp/irrXMLWrapper.h
deleted file mode 100644
index ec8ee7c76e..0000000000
--- a/thirdparty/assimp/include/assimp/irrXMLWrapper.h
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
-Open Asset Import Library (assimp)
-----------------------------------------------------------------------
-
-Copyright (c) 2006-2019, assimp team
-
-
-All rights reserved.
-
-Redistribution and use of this software in source and binary forms,
-with or without modification, are permitted provided that the
-following conditions are met:
-
-* Redistributions of source code must retain the above
-copyright notice, this list of conditions and the
-following disclaimer.
-
-* Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the
-following disclaimer in the documentation and/or other
-materials provided with the distribution.
-
-* Neither the name of the assimp team, nor the names of its
-contributors may be used to endorse or promote products
-derived from this software without specific prior
-written permission of the assimp team.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-----------------------------------------------------------------------
-*/
-
-#ifndef INCLUDED_AI_IRRXML_WRAPPER
-#define INCLUDED_AI_IRRXML_WRAPPER
-
-// some long includes ....
-#include <irrXML.h>
-#include "IOStream.hpp"
-#include "BaseImporter.h"
-#include <vector>
-
-namespace Assimp {
-
-// ---------------------------------------------------------------------------------
-/** @brief Utility class to make IrrXML work together with our custom IO system
- * See the IrrXML docs for more details.
- *
- * Construct IrrXML-Reader in BaseImporter::InternReadFile():
- * @code
- * // open the file
- * std::unique_ptr<IOStream> file( pIOHandler->Open( pFile));
- * if( file.get() == NULL) {
- * throw DeadlyImportError( "Failed to open file " + pFile + ".");
- * }
- *
- * // generate a XML reader for it
- * std::unique_ptr<CIrrXML_IOStreamReader> mIOWrapper( new CIrrXML_IOStreamReader( file.get()));
- * mReader = irr::io::createIrrXMLReader( mIOWrapper.get());
- * if( !mReader) {
- * ThrowException( "xxxx: Unable to open file.");
- * }
- * @endcode
- **/
-class CIrrXML_IOStreamReader : public irr::io::IFileReadCallBack {
-public:
-
- // ----------------------------------------------------------------------------------
- //! Construction from an existing IOStream
- explicit CIrrXML_IOStreamReader(IOStream* _stream)
- : stream (_stream)
- , t (0)
- {
-
- // Map the buffer into memory and convert it to UTF8. IrrXML provides its
- // own conversion, which is merely a cast from uintNN_t to uint8_t. Thus,
- // it is not suitable for our purposes and we have to do it BEFORE IrrXML
- // gets the buffer. Sadly, this forces us to map the whole file into
- // memory.
-
- data.resize(stream->FileSize());
- stream->Read(&data[0],data.size(),1);
-
- // Remove null characters from the input sequence otherwise the parsing will utterly fail
- unsigned int size = 0;
- unsigned int size_max = static_cast<unsigned int>(data.size());
- for(unsigned int i = 0; i < size_max; i++) {
- if(data[i] != '\0') {
- data[size++] = data[i];
- }
- }
- data.resize(size);
-
- BaseImporter::ConvertToUTF8(data);
- }
-
- // ----------------------------------------------------------------------------------
- //! Virtual destructor
- virtual ~CIrrXML_IOStreamReader() {}
-
- // ----------------------------------------------------------------------------------
- //! Reads an amount of bytes from the file.
- /** @param buffer: Pointer to output buffer.
- * @param sizeToRead: Amount of bytes to read
- * @return Returns how much bytes were read. */
- virtual int read(void* buffer, int sizeToRead) {
- if(sizeToRead<0) {
- return 0;
- }
- if(t+sizeToRead>data.size()) {
- sizeToRead = static_cast<int>(data.size()-t);
- }
-
- memcpy(buffer,&data.front()+t,sizeToRead);
-
- t += sizeToRead;
- return sizeToRead;
- }
-
- // ----------------------------------------------------------------------------------
- //! Returns size of file in bytes
- virtual int getSize() {
- return (int)data.size();
- }
-
-private:
- IOStream* stream;
- std::vector<char> data;
- size_t t;
-
-}; // ! class CIrrXML_IOStreamReader
-
-} // ! Assimp
-
-#endif // !! INCLUDED_AI_IRRXML_WRAPPER
diff --git a/thirdparty/assimp/include/assimp/scene.h b/thirdparty/assimp/include/assimp/scene.h
index df5d6f3b5e..2667db85b3 100644
--- a/thirdparty/assimp/include/assimp/scene.h
+++ b/thirdparty/assimp/include/assimp/scene.h
@@ -56,9 +56,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "material.h"
#include "anim.h"
#include "metadata.h"
-#include <cstdlib>
#ifdef __cplusplus
+# include <cstdlib>
extern "C" {
#endif