diff options
| author | Juan Linietsky | 2014-02-09 22:10:30 -0300 |
|---|---|---|
| committer | Juan Linietsky | 2014-02-09 22:10:30 -0300 |
| commit | 0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac (patch) | |
| tree | 276c4d099e178eb67fbd14f61d77b05e3808e9e3 /tools/editor/io_plugins | |
| parent | 0e49da1687bc8192ed210947da52c9e5c5f301bb (diff) | |
| download | godot-0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac.tar.gz godot-0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac.tar.zst godot-0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac.zip | |
GODOT IS OPEN SOURCE
Diffstat (limited to 'tools/editor/io_plugins')
| -rw-r--r-- | tools/editor/io_plugins/SCsub | 7 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_atlas.cpp | 159 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_atlas.h | 43 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_font_import_plugin.cpp | 1298 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_font_import_plugin.h | 56 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_import_collada.cpp | 2200 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_import_collada.h | 51 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_sample_import_plugin.cpp | 630 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_sample_import_plugin.h | 54 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_scene_import_plugin.cpp | 1599 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_scene_import_plugin.h | 167 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_texture_import_plugin.cpp | 1177 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_texture_import_plugin.h | 147 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_translation_import_plugin.cpp | 457 | ||||
| -rw-r--r-- | tools/editor/io_plugins/editor_translation_import_plugin.h | 54 |
15 files changed, 8099 insertions, 0 deletions
diff --git a/tools/editor/io_plugins/SCsub b/tools/editor/io_plugins/SCsub new file mode 100644 index 000000000..b525fb3f7 --- /dev/null +++ b/tools/editor/io_plugins/SCsub @@ -0,0 +1,7 @@ +Import('env') +Export('env') +env.add_source_files(env.tool_sources,"*.cpp") + + + + diff --git a/tools/editor/io_plugins/editor_atlas.cpp b/tools/editor/io_plugins/editor_atlas.cpp new file mode 100644 index 000000000..4c716874b --- /dev/null +++ b/tools/editor/io_plugins/editor_atlas.cpp @@ -0,0 +1,159 @@ +/*************************************************************************/ +/* editor_atlas.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "editor_atlas.h" + + +struct _EditorAtlasWorkRect { + + Size2i s; + Point2i p; + int idx; + _FORCE_INLINE_ bool operator<(const _EditorAtlasWorkRect& p_r) const { return s.width > p_r.s.width; }; +}; + +struct _EditorAtlasWorkRectResult { + + Vector<_EditorAtlasWorkRect> result; + int max_w; + int max_h; +}; + +void EditorAtlas::fit(const Vector<Size2i>& p_rects,Vector<Point2i>& r_result, Size2i& r_size) { + + //super simple, almost brute force scanline stacking fitter + //it's pretty basic for now, but it tries to make sure that the aspect ratio of the + //resulting atlas is somehow square. This is necesary because video cards have limits + //on texture size (usually 2048 or 4096), so the more square a texture, the more chances + //it will work in every hardware. + // for example, it will prioritize a 1024x1024 atlas (works everywhere) instead of a + // 256x8192 atlas (won't work anywhere). + + ERR_FAIL_COND(p_rects.size()==0); + + Vector<_EditorAtlasWorkRect> wrects; + wrects.resize(p_rects.size()); + for(int i=0;i<p_rects.size();i++) { + wrects[i].s=p_rects[i]; + wrects[i].idx=i; + } + wrects.sort(); + int widest = wrects[0].s.width; + + Vector<_EditorAtlasWorkRectResult> results; + + for(int i=0;i<=12;i++) { + + int w = 1<<i; + int max_h=0; + int max_w=0; + if ( w < widest ) + continue; + + Vector<int> hmax; + hmax.resize(w); + for(int j=0;j<w;j++) + hmax[j]=0; + + //place them + int ofs=0; + int limit_h=0; + for(int j=0;j<wrects.size();j++) { + + + if (ofs+wrects[j].s.width > w) { + + ofs=0; + } + + int from_y=0; + for(int k=0;k<wrects[j].s.width;k++) { + + if (hmax[ofs+k] > from_y) + from_y=hmax[ofs+k]; + } + + wrects[j].p.x=ofs; + wrects[j].p.y=from_y; + int end_h = from_y+wrects[j].s.height; + int end_w = ofs+wrects[j].s.width; + if (ofs==0) + limit_h=end_h; + + for(int k=0;k<wrects[j].s.width;k++) { + + hmax[ofs+k]=end_h; + } + + if (end_h > max_h) + max_h=end_h; + + if (end_w > max_w) + max_w=end_w; + + if (ofs==0 || end_h>limit_h ) //while h limit not reched, keep stacking + ofs+=wrects[j].s.width; + + } + + _EditorAtlasWorkRectResult result; + result.result=wrects; + result.max_h=max_h; + result.max_w=max_w; + results.push_back(result); + + } + + //find the result with the best aspect ratio + + int best=-1; + float best_aspect=1e20; + + for(int i=0;i<results.size();i++) { + + float h = nearest_power_of_2(results[i].max_h); + float w = nearest_power_of_2(results[i].max_w); + float aspect = h>w ? h/w : w/h; + if (aspect < best_aspect) { + best=i; + best_aspect=aspect; + } + } + + r_result.resize(p_rects.size()); + + for(int i=0;i<p_rects.size();i++) { + + r_result[ results[best].result[i].idx ]=results[best].result[i].p; + } + + r_size=Size2(results[best].max_w,results[best].max_h ); + +} + + diff --git a/tools/editor/io_plugins/editor_atlas.h b/tools/editor/io_plugins/editor_atlas.h new file mode 100644 index 000000000..685cf60c9 --- /dev/null +++ b/tools/editor/io_plugins/editor_atlas.h @@ -0,0 +1,43 @@ +/*************************************************************************/ +/* editor_atlas.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_ATLAS_H +#define EDITOR_ATLAS_H + +#include "math_2d.h" +#include "vector.h" + +class EditorAtlas { +public: + + static void fit(const Vector<Size2i>& p_rects,Vector<Point2i>& r_result, Size2i& r_size); + + +}; + +#endif // EDITOR_ATLAS_H diff --git a/tools/editor/io_plugins/editor_font_import_plugin.cpp b/tools/editor/io_plugins/editor_font_import_plugin.cpp new file mode 100644 index 000000000..d64a2fd42 --- /dev/null +++ b/tools/editor/io_plugins/editor_font_import_plugin.cpp @@ -0,0 +1,1298 @@ +/*************************************************************************/ +/* editor_font_import_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "editor_font_import_plugin.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/file_dialog.h" +#include "tools/editor/editor_node.h" +#include "os/file_access.h" +#include "editor_atlas.h" +#include "io/image_loader.h" +#include "io/resource_saver.h" +#ifdef FREETYPE_ENABLED + +#include <ft2build.h> +#include FT_FREETYPE_H + +#endif + + +class _EditorFontImportOptions : public Object { + + OBJ_TYPE(_EditorFontImportOptions,Object); +public: + + enum ColorType { + COLOR_WHITE, + COLOR_CUSTOM, + COLOR_GRADIENT_RANGE, + COLOR_GRADIENT_IMAGE + }; + + + int char_extra_spacing; + int top_extra_spacing; + int bottom_extra_spacing; + int space_extra_spacing; + + enum CharacterSet { + + CHARSET_ASCII, + CHARSET_LATIN, + CHARSET_UNICODE, + CHARSET_CUSTOM, + CHARSET_CUSTOM_LATIN + }; + + CharacterSet character_set; + String custom_file; + + bool shadow; + Vector2 shadow_offset; + int shadow_radius; + Color shadow_color; + float shadow_transition; + + bool shadow2; + Vector2 shadow2_offset; + int shadow2_radius; + Color shadow2_color; + float shadow2_transition; + + ColorType color_type; + Color color; + Color gradient_begin; + Color gradient_end; + String gradient_image; + + + bool round_advance; + + + + bool _set(const StringName& p_name, const Variant& p_value) { + + String n = p_name; + if (n=="extra_space/char") + char_extra_spacing=p_value; + else if (n=="extra_space/space") + space_extra_spacing=p_value; + else if (n=="extra_space/top") + top_extra_spacing=p_value; + else if (n=="extra_space/bottom") + bottom_extra_spacing=p_value; + + else if (n=="character_set/mode") { + character_set=CharacterSet(int(p_value)); + _change_notify(); + } else if (n=="character_set/custom") + custom_file=p_value; + + else if (n=="shadow/enabled") { + shadow=p_value; + _change_notify(); + }else if (n=="shadow/radius") + shadow_radius=p_value; + else if (n=="shadow/offset") + shadow_offset=p_value; + else if (n=="shadow/color") + shadow_color=p_value; + else if (n=="shadow/transition") + shadow_transition=p_value; + + else if (n=="shadow2/enabled") { + shadow2=p_value; + _change_notify(); + }else if (n=="shadow2/radius") + shadow2_radius=p_value; + else if (n=="shadow2/offset") + shadow2_offset=p_value; + else if (n=="shadow2/color") + shadow2_color=p_value; + else if (n=="shadow2/transition") + shadow2_transition=p_value; + + else if (n=="color/mode") { + color_type=ColorType(int(p_value)); + _change_notify(); + }else if (n=="color/color") + color=p_value; + else if (n=="color/begin") + gradient_begin=p_value; + else if (n=="color/end") + gradient_end=p_value; + else if (n=="color/image") + gradient_image=p_value; + else if (n=="advanced/round_advance") + round_advance=p_value; + else + return false; + + emit_signal("changed"); + + + return true; + + } + + bool _get(const StringName& p_name,Variant &r_ret) const{ + + String n = p_name; + if (n=="extra_space/char") + r_ret=char_extra_spacing; + else if (n=="extra_space/space") + r_ret=space_extra_spacing; + else if (n=="extra_space/top") + r_ret=top_extra_spacing; + else if (n=="extra_space/bottom") + r_ret=bottom_extra_spacing; + + else if (n=="character_set/mode") + r_ret=character_set; + else if (n=="character_set/custom") + r_ret=custom_file; + + else if (n=="shadow/enabled") + r_ret=shadow; + else if (n=="shadow/radius") + r_ret=shadow_radius; + else if (n=="shadow/offset") + r_ret=shadow_offset; + else if (n=="shadow/color") + r_ret=shadow_color; + else if (n=="shadow/transition") + r_ret=shadow_transition; + + else if (n=="shadow2/enabled") + r_ret=shadow2; + else if (n=="shadow2/radius") + r_ret=shadow2_radius; + else if (n=="shadow2/offset") + r_ret=shadow2_offset; + else if (n=="shadow2/color") + r_ret=shadow2_color; + else if (n=="shadow2/transition") + r_ret=shadow2_transition; + + + else if (n=="color/mode") + r_ret=color_type; + else if (n=="color/color") + r_ret=color; + else if (n=="color/begin") + r_ret=gradient_begin; + else if (n=="color/end") + r_ret=gradient_end; + else if (n=="color/image") + r_ret=gradient_image; + else if (n=="advanced/round_advance") + r_ret=round_advance; + else + return false; + + return true; + + } + + void _get_property_list( List<PropertyInfo> *p_list) const{ + + p_list->push_back(PropertyInfo(Variant::INT,"extra_space/char",PROPERTY_HINT_RANGE,"-64,64,1")); + p_list->push_back(PropertyInfo(Variant::INT,"extra_space/space",PROPERTY_HINT_RANGE,"-64,64,1")); + p_list->push_back(PropertyInfo(Variant::INT,"extra_space/top",PROPERTY_HINT_RANGE,"-64,64,1")); + p_list->push_back(PropertyInfo(Variant::INT,"extra_space/bottom",PROPERTY_HINT_RANGE,"-64,64,1")); + p_list->push_back(PropertyInfo(Variant::INT,"character_set/mode",PROPERTY_HINT_ENUM,"Ascii,Latin,Unicode,Custom,Custom&Latin")); + + if (character_set>=CHARSET_CUSTOM) + p_list->push_back(PropertyInfo(Variant::STRING,"character_set/custom",PROPERTY_HINT_FILE)); + + p_list->push_back(PropertyInfo(Variant::BOOL,"shadow/enabled")); + if (shadow) { + p_list->push_back(PropertyInfo(Variant::INT,"shadow/radius",PROPERTY_HINT_RANGE,"-64,64,1")); + p_list->push_back(PropertyInfo(Variant::VECTOR2,"shadow/offset")); + p_list->push_back(PropertyInfo(Variant::COLOR,"shadow/color")); + p_list->push_back(PropertyInfo(Variant::REAL,"shadow/transition",PROPERTY_HINT_EXP_EASING)); + } + + p_list->push_back(PropertyInfo(Variant::BOOL,"shadow2/enabled")); + if (shadow2) { + p_list->push_back(PropertyInfo(Variant::INT,"shadow2/radius",PROPERTY_HINT_RANGE,"-64,64,1")); + p_list->push_back(PropertyInfo(Variant::VECTOR2,"shadow2/offset")); + p_list->push_back(PropertyInfo(Variant::COLOR,"shadow2/color")); + p_list->push_back(PropertyInfo(Variant::REAL,"shadow2/transition",PROPERTY_HINT_EXP_EASING)); + } + + p_list->push_back(PropertyInfo(Variant::INT,"color/mode",PROPERTY_HINT_ENUM,"White,Color,Gradient,Gradient Image")); + if (color_type==COLOR_CUSTOM) { + p_list->push_back(PropertyInfo(Variant::COLOR,"color/color")); + + } + if (color_type==COLOR_GRADIENT_RANGE) { + p_list->push_back(PropertyInfo(Variant::COLOR,"color/begin")); + p_list->push_back(PropertyInfo(Variant::COLOR,"color/end")); + } + if (color_type==COLOR_GRADIENT_IMAGE) { + p_list->push_back(PropertyInfo(Variant::STRING,"color/image",PROPERTY_HINT_FILE)); + } + p_list->push_back(PropertyInfo(Variant::BOOL,"advanced/round_advance")); + + } + + + static void _bind_methods() { + + + ADD_SIGNAL( MethodInfo("changed")); + } + + + _EditorFontImportOptions() { + + char_extra_spacing=0; + top_extra_spacing=0; + bottom_extra_spacing=0; + space_extra_spacing=0; + + character_set=CHARSET_LATIN; + + shadow=false; + shadow_radius=2; + shadow_color=Color(0,0,0,0.3); + shadow_transition=1.0; + + shadow2=false; + shadow2_radius=2; + shadow2_color=Color(0,0,0,0.3); + shadow2_transition=1.0; + + color_type=COLOR_WHITE; + color=Color(1,1,1,1); + gradient_begin=Color(1,1,1,1); + gradient_end=Color(0.5,0.5,0.5,1); + + round_advance=true; + } + + +}; + + +class EditorFontImportDialog : public ConfirmationDialog { + + OBJ_TYPE(EditorFontImportDialog, ConfirmationDialog); + + + LineEditFileChooser *source; + LineEditFileChooser *dest; + SpinBox *font_size; + LineEdit *test_string; + ColorPickerButton *test_color; + Label *test_label; + PropertyEditor *prop_edit; + Timer *timer; + ConfirmationDialog *error_dialog; + + + Ref<ResourceImportMetadata> get_rimd() { + + Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata ); + List<PropertyInfo> pl; + options->_get_property_list(&pl); + for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) { + + Variant v; + String opt=E->get().name; + options->_get(opt,v); + if (opt=="color/image" || opt=="character_set/custom") { + v = EditorImportPlugin::validate_source_path(v); + } + imd->set_option(opt,v); + } + + imd->add_source(EditorImportPlugin::validate_source_path(source->get_line_edit()->get_text())); + imd->set_option("font/size",font_size->get_val()); + + return imd; + + } + + void _src_changed(String) { + _prop_changed(); + } + + void _update_text2(String) { + _update_text(); + } + void _update_text3(Color) { + _update_text(); + } + + void _update_text() { + + test_label->set_text(""); + test_label->set_text(test_string->get_text()); + test_label->add_color_override("font_color",test_color->get_color()); + } + + void _update() { + + Ref<ResourceImportMetadata> imd = get_rimd(); + Ref<Font> font = plugin->generate_font(imd); + test_label->add_font_override("font",font); + _update_text(); + } + + void _font_size_changed(double) { + + _prop_changed(); + } + + void _prop_changed() { + + timer->start(); + } + + void _import_inc(String p_font) { + + Ref<Font> font = ResourceLoader::load(p_font); + if (!font.is_valid()) + return; + Ref<ImageTexture> tex = font->get_texture(0); + if (tex.is_null()) + return; + FileAccessRef f=FileAccess::open(p_font.basename()+".inc",FileAccess::WRITE); + Vector<CharType> ck = font->get_char_keys(); + + f->store_line("static const int _builtin_font_height="+itos(font->get_height())+";"); + f->store_line("static const int _builtin_font_ascent="+itos(font->get_ascent())+";"); + f->store_line("static const int _builtin_font_charcount="+itos(ck.size())+";"); + f->store_line("static const int _builtin_font_charrects["+itos(ck.size())+"][8]={"); + f->store_line("/* charidx , ofs_x, ofs_y, size_x, size_y, valign, halign, advance */"); + + for(int i=0;i<ck.size();i++) { + CharType k=ck[i]; + Font::Character c=font->get_character(k); + f->store_line("{"+itos(k)+","+rtos(c.rect.pos.x)+","+rtos(c.rect.pos.y)+","+rtos(c.rect.size.x)+","+rtos(c.rect.size.y)+","+rtos(c.v_align)+","+rtos(c.h_align)+","+rtos(c.advance)+"},"); + } + f->store_line("};"); + + Vector<Font::KerningPairKey> kp=font->get_kerning_pair_keys(); + f->store_line("static const int _builtin_font_kerning_pair_count="+itos(kp.size())+";"); + f->store_line("static const int _builtin_font_kerning_pairs["+itos(kp.size())+"][3]={"); + for(int i=0;i<kp.size();i++) { + + int d = font->get_kerning_pair(kp[i].A,kp[i].B); + f->store_line("{"+itos(kp[i].A)+","+itos(kp[i].B)+","+itos(d)+"},"); + } + + f->store_line("};"); + Image img = tex->get_data(); + + f->store_line("static const int _builtin_font_img_width="+itos(img.get_width())+";"); + f->store_line("static const int _builtin_font_img_height="+itos(img.get_height())+";"); + f->store_line("static const unsigned char _builtin_font_img_data["+itos(img.get_width()*img.get_height()*2)+"]={"); + for(int i=0;i<img.get_height();i++) { + + for(int j=0;j<img.get_width();j++) { + + Color c = img.get_pixel(j,i); + int v = CLAMP(((c.r+c.g+c.b)/3.0)*255,0,255); + int a = CLAMP(c.a*255,0,255); + + f->store_line(itos(v)+","+itos(a)+","); + } + } + f->store_line("};"); + + } + + void _import() { + + if (source->get_line_edit()->get_text()=="") { + error_dialog->set_text("No source font file!"); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + if (dest->get_line_edit()->get_text()=="") { + error_dialog->set_text("No tatget font resource!"); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + Ref<ResourceImportMetadata> rimd = get_rimd(); + + if (rimd.is_null()) { + error_dialog->set_text("Can't load/process source font"); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + Error err = plugin->import(dest->get_line_edit()->get_text(),rimd); + + if (err!=OK) { + error_dialog->set_text("Could't save font."); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + //_import_inc(dest->get_line_edit()->get_text()); + + hide(); + } + + EditorFontImportPlugin *plugin; + _EditorFontImportOptions *options; + + static void _bind_methods() { + + ObjectTypeDB::bind_method("_update",&EditorFontImportDialog::_update); + ObjectTypeDB::bind_method("_update_text",&EditorFontImportDialog::_update_text); + ObjectTypeDB::bind_method("_update_text2",&EditorFontImportDialog::_update_text2); + ObjectTypeDB::bind_method("_update_text3",&EditorFontImportDialog::_update_text3); + ObjectTypeDB::bind_method("_prop_changed",&EditorFontImportDialog::_prop_changed); + ObjectTypeDB::bind_method("_src_changed",&EditorFontImportDialog::_src_changed); + ObjectTypeDB::bind_method("_font_size_changed",&EditorFontImportDialog::_font_size_changed); + ObjectTypeDB::bind_method("_import",&EditorFontImportDialog::_import); + + } + +public: + + void _notification(int p_what) { + + if (p_what==NOTIFICATION_ENTER_SCENE) { + prop_edit->edit(options); + _update_text(); + } + } + + void popup_import(const String& p_path) { + + popup_centered(Size2(600,500)); + + if (p_path!="") { + + Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path); + ERR_FAIL_COND(!rimd.is_valid()); + + dest->get_line_edit()->set_text(p_path); + List<String> opts; + rimd->get_options(&opts); + for(List<String>::Element *E=opts.front();E;E=E->next()) { + + options->_set(E->get(),rimd->get_option(E->get())); + } + + String src = ""; + for(int i=0;i<rimd->get_source_count();i++) { + if (i>0) + src+=","; + src+=EditorImportPlugin::expand_source_path(rimd->get_source_path(i)); + } + source->get_line_edit()->set_text(src); + + font_size->set_val(rimd->get_option("font/size")); + } + } + + EditorFontImportDialog(EditorFontImportPlugin *p_plugin) { + plugin=p_plugin; + VBoxContainer *vbc = memnew( VBoxContainer ); + add_child(vbc); + set_child_rect(vbc); + HBoxContainer *hbc = memnew( HBoxContainer); + vbc->add_child(hbc); + VBoxContainer *vbl = memnew( VBoxContainer ); + hbc->add_child(vbl); + hbc->set_v_size_flags(SIZE_EXPAND_FILL); + vbl->set_h_size_flags(SIZE_EXPAND_FILL); + VBoxContainer *vbr = memnew( VBoxContainer ); + hbc->add_child(vbr); + vbr->set_h_size_flags(SIZE_EXPAND_FILL); + + source = memnew( LineEditFileChooser ); + source->get_file_dialog()->set_access(FileDialog::ACCESS_FILESYSTEM); + source->get_file_dialog()->set_mode(FileDialog::MODE_OPEN_FILE); + source->get_file_dialog()->add_filter("*.ttf;TrueType"); + source->get_file_dialog()->add_filter("*.otf;OpenType"); + source->get_line_edit()->connect("text_entered",this,"_src_changed"); + + vbl->add_margin_child("Source Font:",source); + font_size = memnew( SpinBox ); + vbl->add_margin_child("Source Font Size:",font_size); + font_size->set_min(3); + font_size->set_max(256); + font_size->set_val(16); + font_size->connect("value_changed",this,"_font_size_changed"); + dest = memnew( LineEditFileChooser ); + // + List<String> fl; + Ref<Font> font= memnew(Font); + dest->get_file_dialog()->add_filter("*.fnt ; Font" ); + //ResourceSaver::get_recognized_extensions(font,&fl); + //for(List<String>::Element *E=fl.front();E;E=E->next()) { + // dest->get_file_dialog()->add_filter("*."+E->get()); + //} + + vbl->add_margin_child("Dest Resource:",dest); + HBoxContainer *testhb = memnew( HBoxContainer ); + test_string = memnew( LineEdit ); + test_string->set_text("The quick brown fox jumps over the lazy dog."); + test_string->set_h_size_flags(SIZE_EXPAND_FILL); + test_string->set_stretch_ratio(5); + + testhb->add_child(test_string); + test_color = memnew( ColorPickerButton ); + test_color->set_color(get_color("font_color","Label")); + test_color->set_h_size_flags(SIZE_EXPAND_FILL); + test_color->set_stretch_ratio(1); + test_color->connect("color_changed",this,"_update_text3"); + testhb->add_child(test_color); + + vbl->add_spacer(); + vbl->add_margin_child("Test: ",testhb); + HBoxContainer *upd_hb = memnew( HBoxContainer ); +// vbl->add_child(upd_hb); + upd_hb->add_spacer(); + Button *update = memnew( Button); + upd_hb->add_child(update); + update->set_text("Update"); + update->connect("pressed",this,"_update"); + + options = memnew( _EditorFontImportOptions ); + prop_edit = memnew( PropertyEditor() ); + vbr->add_margin_child("Options:",prop_edit,true); + options->connect("changed",this,"_prop_changed"); + + prop_edit->hide_top_label(); + + Panel *panel = memnew( Panel ); + vbc->add_child(panel); + test_label = memnew( Label ); + test_label->set_autowrap(true); + panel->add_child(test_label); + test_label->set_area_as_parent_rect(); + panel->set_v_size_flags(SIZE_EXPAND_FILL); + test_string->connect("text_changed",this,"_update_text2"); + set_title("Font Import"); + timer = memnew( Timer ); + add_child(timer); + timer->connect("timeout",this,"_update"); + timer->set_wait_time(0.4); + timer->set_one_shot(true); + + get_ok()->connect("pressed", this,"_import"); + get_ok()->set_text("Import"); + + error_dialog = memnew ( ConfirmationDialog ); + add_child(error_dialog); + error_dialog->get_ok()->set_text("Accept"); + set_hide_on_ok(false); + + + } + + ~EditorFontImportDialog() { + memdelete(options); + } +}; + + +/////////////////////////////////////// + + + +struct _EditorFontData { + + Vector<uint8_t> bitmap; + int width,height; + int ofs_x; //ofset to center, from ABOVE + int ofs_y; //ofset to begining, from LEFT + int valign; //vertical alignment + int halign; + float advance; + int character; + int glyph; + + int texture; + Image blit; + Point2i blit_ofs; +// bool printable; + +}; + + +struct _EditorFontDataSort { + + bool operator()(const _EditorFontData *p_A,const _EditorFontData *p_B) const { + return p_A->height > p_B->height; + }; +}; + +struct _EditorKerningKey { + + CharType A,B; + bool operator<(const _EditorKerningKey& p_k) const { return (A==p_k.A)?(B<p_k.B):(A<p_k.A); } + +}; + +Ref<Font> EditorFontImportPlugin::generate_font(const Ref<ResourceImportMetadata>& p_from, const String &p_existing) { + + Ref<ResourceImportMetadata> from = p_from; + ERR_FAIL_COND_V(from->get_source_count()!=1,Ref<Font>()); + + String src_path = EditorImportPlugin::expand_source_path(from->get_source_path(0)); + int size = from->get_option("font/size"); + +#ifdef FREETYPE_ENABLED + FT_Library library; /* handle to library */ + FT_Face face; /* handle to face object */ + + Vector<_EditorFontData*> font_data_list; + + int error = FT_Init_FreeType( &library ); + + ERR_EXPLAIN("Error initializing FreeType."); + ERR_FAIL_COND_V( error !=0, Ref<Font>() ); + + print_line("loadfrom: "+src_path); + error = FT_New_Face( library, src_path.utf8().get_data(),0,&face ); + + if ( error == FT_Err_Unknown_File_Format ) { + ERR_EXPLAIN("Unknown font format."); + FT_Done_FreeType( library ); + } else if ( error ) { + + ERR_EXPLAIN("Error loading font."); + FT_Done_FreeType( library ); + + } + + ERR_FAIL_COND_V(error,Ref<Font>()); + + + int height=0; + int ascent=0; + int font_spacing=0; + + error = FT_Set_Char_Size(face,0,64*size,512,512); + + if ( error ) { + FT_Done_FreeType( library ); + ERR_EXPLAIN("Invalid font size. "); + ERR_FAIL_COND_V( error,Ref<Font>() ); + + } + + error = FT_Set_Pixel_Sizes(face,0,size); + + FT_GlyphSlot slot = face->glyph; + +// error = FT_Set_Charmap(face,ft_encoding_unicode ); /* encoding.. */ + + + /* PRINT CHARACTERS TO INDIVIDUAL BITMAPS */ + + +// int space_size=5; //size for space, if none found.. 5! +// int min_valign=500; //some ridiculous number + + FT_ULong charcode; + FT_UInt gindex; + + int max_up=-1324345; ///gibberish + int max_down=124232; + + Map<_EditorKerningKey,int> kerning_map; + + charcode = FT_Get_First_Char( face, &gindex ); + + Set<CharType> import_chars; + + int import_mode = from->get_option("character_set/mode"); + bool round_advance = from->get_option("advanced/round_advance"); + + if (import_mode>=_EditorFontImportOptions::CHARSET_CUSTOM) { + + //load from custom text + String path = from->get_option("character_set/custom"); + + FileAccess *fa = FileAccess::open(EditorImportPlugin::expand_source_path(path),FileAccess::READ); + + if ( !fa ) { + + FT_Done_FreeType( library ); + ERR_EXPLAIN("Invalid font custom source. "); + ERR_FAIL_COND_V( !fa,Ref<Font>() ); + + } + + + while(!fa->eof_reached()) { + + String line = fa->get_line(); + for(int i=0;i<line.length();i++) { + import_chars.insert(line[i]); + } + } + + if (import_mode==_EditorFontImportOptions::CHARSET_CUSTOM_LATIN) { + + for(int i=32;i<128;i++) + import_chars.insert(i); + } + + memdelete(fa); + } + + int xsize=0; + while ( gindex != 0 ) + { + + bool skip=false; + error = FT_Load_Char( face, charcode, FT_LOAD_RENDER ); + if (error) skip=true; + else error = FT_Render_Glyph( face->glyph, ft_render_mode_normal ); + if (error) { + skip=true; + } else if (!skip) { + + switch(import_mode) { + + case _EditorFontImportOptions::CHARSET_ASCII: skip = charcode>127; break; + case _EditorFontImportOptions::CHARSET_LATIN: skip = charcode>255 ;break; + case _EditorFontImportOptions::CHARSET_UNICODE: break; //none + case _EditorFontImportOptions::CHARSET_CUSTOM: + case _EditorFontImportOptions::CHARSET_CUSTOM_LATIN: skip = !import_chars.has(charcode); break; + + } + } + + if (charcode<=32) //?? + skip=true; + + if (skip) { + charcode=FT_Get_Next_Char(face,charcode,&gindex); + continue; + } + + _EditorFontData * fdata = memnew( _EditorFontData ); + fdata->bitmap.resize( slot->bitmap.width*slot->bitmap.rows ); + fdata->width=slot->bitmap.width; + fdata->height=slot->bitmap.rows; + fdata->character=charcode; + fdata->glyph=FT_Get_Char_Index(face,charcode); + if (charcode=='x') + xsize=slot->bitmap.width; + + + if (charcode<127) { + if (slot->bitmap_top>max_up) { + + max_up=slot->bitmap_top; + } + + + if ( (slot->bitmap_top - fdata->height)<max_down ) { + + max_down=slot->bitmap_top - fdata->height; + } + } + + + fdata->valign=slot->bitmap_top; + fdata->halign=slot->bitmap_left; + + if (round_advance) + fdata->advance=(slot->advance.x+(1<<5))>>6; + else + fdata->advance=slot->advance.x/float(1<<6); + + fdata->advance+=font_spacing; + + for (int i=0;i<slot->bitmap.width;i++) { + for (int j=0;j<slot->bitmap.rows;j++) { + + fdata->bitmap[j*slot->bitmap.width+i]=slot->bitmap.buffer[j*slot->bitmap.width+i]; + } + } + + font_data_list.push_back(fdata); + charcode=FT_Get_Next_Char(face,charcode,&gindex); +// printf("reading char %i\n",charcode); + } + + /* SPACE */ + + _EditorFontData *spd = memnew( _EditorFontData ); + spd->advance=0; + spd->character=' '; + spd->halign=0; + spd->valign=0; + spd->width=0; + spd->height=0; + spd->ofs_x=0; + spd->ofs_y=0; + + if (!FT_Load_Char( face, ' ', FT_LOAD_RENDER ) && !FT_Render_Glyph( face->glyph, ft_render_mode_normal )) { + + spd->advance = slot->advance.x>>6; //round to nearest or store as float + spd->advance+=font_spacing; + } else { + + spd->advance=xsize; + spd->advance+=font_spacing; + } + + font_data_list.push_back(spd); + + Set<CharType> exported; + for (int i=0; i<font_data_list.size(); i++) { + exported.insert(font_data_list[i]->character); + }; + int missing = 0; + for(Set<CharType>::Element *E=import_chars.front();E;E=E->next()) { + CharType c = E->get(); + if (!exported.has(c)) { + CharType str[2] = {c, 0}; + printf("** Warning: character %i (%ls) not exported\n", (int)c, str); + ++missing; + }; + }; + printf("total %i/%i\n", missing, import_chars.size()); + + /* KERNING */ + + + for(int i=0;i<font_data_list.size();i++) { + + for(int j=0;j<font_data_list.size();j++) { + + FT_Vector delta; + FT_Get_Kerning( face, font_data_list[i]->glyph,font_data_list[j]->glyph, FT_KERNING_DEFAULT, &delta ); + + if (delta.x!=0) { + + _EditorKerningKey kpk; + kpk.A = font_data_list[i]->character; + kpk.B = font_data_list[j]->character; + int kern = ((-delta.x)+(1<<5))>>6; + + if (kern==0) + continue; + kerning_map[kpk]=kern; + } + } + } + + height=max_up-max_down; + ascent=max_up; + + /* FIND OUT WHAT THE FONT HEIGHT FOR THIS IS */ + + /* ADJUST THE VALIGN FOR EACH CHARACTER */ + + for (int i=0;i<(int)font_data_list.size();i++) { + + font_data_list[i]->valign=max_up-font_data_list[i]->valign; + } + + + + /* ADD THE SPACEBAR CHARACTER */ +/* + _EditorFontData * fdata = new _EditorFontData; + + fdata->character=32; + fdata->bitmap=0; + fdata->width=xsize; + fdata->height=1; + fdata->valign=0; + + font_data_list.push_back(fdata); +*/ + /* SORT BY HEIGHT, SO THEY FIT BETTER ON THE TEXTURE */ + + font_data_list.sort_custom<_EditorFontDataSort>(); + Color *color=memnew_arr(Color,height); + + int gradient_type=from->get_option("color/mode"); + switch(gradient_type) { + case _EditorFontImportOptions::COLOR_WHITE: { + + for(int i=0;i<height;i++){ + color[i]=Color(1,1,1,1); + } + + } break; + case _EditorFontImportOptions::COLOR_CUSTOM: { + + Color cc = from->get_option("color/color"); + for(int i=0;i<height;i++){ + color[i]=cc; + } + + } break; + case _EditorFontImportOptions::COLOR_GRADIENT_RANGE: { + + Color src=from->get_option("color/begin"); + Color to=from->get_option("color/end"); + for(int i=0;i<height;i++){ + color[i]=src.linear_interpolate(to,i/float(height)); + } + + } break; + case _EditorFontImportOptions::COLOR_GRADIENT_IMAGE: { + + String fp = EditorImportPlugin::expand_source_path(from->get_option("color/image")); + Image img; + Error err = ImageLoader::load_image(fp,&img); + if (err==OK) { + + for(int i=0;i<height;i++){ + color[i]=img.get_pixel(0,i*img.get_height()/height); + } + } else { + + for(int i=0;i<height;i++){ + color[i]=Color(1,1,1,1); + } + } + + } break; + } + + + for(int i=0;i<font_data_list.size();i++) { + + if (font_data_list[i]->bitmap.size()==0) + continue; + + int margin[4]={0,0,0,0}; + + if (from->get_option("shadow/enabled").operator bool()) { + int r=from->get_option("shadow/radius"); + Point2i ofs=Point2(from->get_option("shadow/offset")); + margin[ MARGIN_LEFT ] = MAX( r - ofs.x, 0); + margin[ MARGIN_RIGHT ] = MAX( r + ofs.x, 0); + margin[ MARGIN_TOP ] = MAX( r - ofs.y, 0); + margin[ MARGIN_BOTTOM ] = MAX( r + ofs.y, 0); + + } + + if (from->get_option("shadow2/enabled").operator bool()) { + int r=from->get_option("shadow2/radius"); + Point2i ofs=Point2(from->get_option("shadow2/offset")); + margin[ MARGIN_LEFT ] = MAX( r - ofs.x, margin[ MARGIN_LEFT ]); + margin[ MARGIN_RIGHT ] = MAX( r + ofs.x, margin[ MARGIN_RIGHT ]); + margin[ MARGIN_TOP ] = MAX( r - ofs.y, margin[ MARGIN_TOP ]); + margin[ MARGIN_BOTTOM ] = MAX( r + ofs.y, margin[ MARGIN_BOTTOM ]); + + } + + Size2i s; + s.width=font_data_list[i]->width+margin[MARGIN_LEFT]+margin[MARGIN_RIGHT]; + s.height=font_data_list[i]->height+margin[MARGIN_TOP]+margin[MARGIN_BOTTOM]; + Point2i o; + o.x=margin[MARGIN_LEFT]; + o.y=margin[MARGIN_TOP]; + + int ow=font_data_list[i]->width; + int oh=font_data_list[i]->height; + + DVector<uint8_t> pixels; + pixels.resize(s.x*s.y*4); + + DVector<uint8_t>::Write w = pixels.write(); + print_line("val: "+itos(font_data_list[i]->valign)); + for(int y=0;y<s.height;y++) { + + int yc=CLAMP(y-o.y+font_data_list[i]->valign,0,height-1); + Color c=color[yc]; + c.a=0; + + for(int x=0;x<s.width;x++) { + + int ofs=y*s.x+x; + w[ofs*4+0]=c.r*255.0; + w[ofs*4+1]=c.g*255.0; + w[ofs*4+2]=c.b*255.0; + w[ofs*4+3]=c.a*255.0; + } + } + + + for(int si=0;si<2;si++) { + +#define S_VAR(m_v) (String(si==0?"shadow/":"shadow2/")+m_v) + if (from->get_option(S_VAR("enabled")).operator bool()) { + int r = from->get_option(S_VAR("radius")); + + Color sc = from->get_option(S_VAR("color")); + Point2i so=Point2(from->get_option(S_VAR("offset"))); + + float tr = from->get_option(S_VAR("transition")); + print_line("shadow enabled: "+itos(si)); + + Vector<uint8_t> s2buf; + s2buf.resize(s.x*s.y); + uint8_t *wa=s2buf.ptr(); + + for(int j=0;j<s.x*s.y;j++){ + + wa[j]=0; + } + + // blit shadowa + for(int x=0;x<ow;x++) { + for(int y=0;y<oh;y++) { + int ofs = (o.y+y+so.y)*s.x+x+o.x+so.x; + wa[ofs]=font_data_list[i]->bitmap[y*ow+x]; + } + } + //blur shadow2 with separatable convolution + + if (r>0) { + + Vector<uint8_t> pixels2; + pixels2.resize(s2buf.size()); + uint8_t *w2=pixels2.ptr(); + //vert + for(int x=0;x<s.width;x++) { + for(int y=0;y<s.height;y++) { + + int ofs = y*s.width+x; + int sum=wa[ofs]; + + for(int k=1;k<=r;k++) { + + int ofs_d=MIN(y+k,s.height-1)*s.width+x; + int ofs_u=MAX(y-k,0)*s.width+x; + sum+=wa[ofs_d]; + sum+=wa[ofs_u]; + } + + w2[ofs]=sum/(r*2+1); + + } + } + //horiz + for(int x=0;x<s.width;x++) { + for(int y=0;y<s.height;y++) { + + int ofs = y*s.width+x; + int sum=w2[ofs]; + + for(int k=1;k<=r;k++) { + + int ofs_r=MIN(x+k,s.width-1)+s.width*y; + int ofs_l=MAX(x-k,0)+s.width*y; + sum+=w2[ofs_r]; + sum+=w2[ofs_l]; + } + + wa[ofs]=Math::pow(float(sum/(r*2+1))/255.0,tr)*255.0; + + } + } + + } + + //blend back + + for(int j=0;j<s.x*s.y;j++){ + Color wd(w[j*4+0]/255.0,w[j*4+1]/255.0,w[j*4+2]/255.0,w[j*4+3]/255.0); + Color ws(sc.r,sc.g,sc.b,sc.a*(wa[j]/255.0)); + Color b = wd.blend(ws); + + w[j*4+0]=b.r*255.0; + w[j*4+1]=b.g*255.0; + w[j*4+2]=b.b*255.0; + w[j*4+3]=b.a*255.0; + + } + } + } + + for(int y=0;y<oh;y++) { + int yc=CLAMP(y+font_data_list[i]->valign,0,height-1); + Color sc=color[yc]; + for(int x=0;x<ow;x++) { + int ofs = (o.y+y)*s.x+x+o.x; + float c = font_data_list[i]->bitmap[y*ow+x]/255.0; + Color src_col=sc; + src_col.a*=c; + Color dst_col(w[ofs*4+0]/255.0,w[ofs*4+1]/255.0,w[ofs*4+2]/255.0,w[ofs*4+3]/255.0); + dst_col = dst_col.blend(src_col); + w[ofs*4+0]=dst_col.r*255.0; + w[ofs*4+1]=dst_col.g*255.0; + w[ofs*4+2]=dst_col.b*255.0; + w[ofs*4+3]=dst_col.a*255.0; + } + } + + + w=DVector<uint8_t>::Write(); + + Image img(s.width,s.height,0,Image::FORMAT_RGBA,pixels); + + font_data_list[i]->blit=img; + font_data_list[i]->blit_ofs=o; + + } + + //make atlas + int spacing=2; + Vector<Size2i> sizes; + sizes.resize(font_data_list.size()); + for(int i=0;i<font_data_list.size();i++) { + + sizes[i]=Size2(font_data_list[i]->blit.get_width()+spacing*2,font_data_list[i]->blit.get_height()+spacing*2); + + } + Vector<Point2i> res; + Size2i res_size; + EditorAtlas::fit(sizes,res,res_size); + res_size.x=nearest_power_of_2(res_size.x); + res_size.y=nearest_power_of_2(res_size.y); + print_line("Atlas size: "+res_size); + + Image atlas(res_size.x,res_size.y,0,Image::FORMAT_RGBA); + + for(int i=0;i<font_data_list.size();i++) { + + if (font_data_list[i]->bitmap.size()==0) + continue; + atlas.blit_rect(font_data_list[i]->blit,Rect2(0,0,font_data_list[i]->blit.get_width(),font_data_list[i]->blit.get_height()),res[i]+Size2(spacing,spacing)); + font_data_list[i]->ofs_x=res[i].x+spacing; + font_data_list[i]->ofs_y=res[i].y+spacing; + + + } + + if (0) { + //debug the texture + Ref<ImageTexture> atlast = memnew( ImageTexture ); + atlast->create_from_image(atlas); +// atlast->create_from_image(font_data_list[5]->blit); + TextureFrame *tf = memnew( TextureFrame ); + tf->set_texture(atlast); + dialog->add_child(tf); + } + + + /* CREATE FONT */ + + int char_space = from->get_option("extra_space/char"); + int space_space = from->get_option("extra_space/space"); + int top_space = from->get_option("extra_space/top"); + int bottom_space = from->get_option("extra_space/bottom"); + + Ref<Font> font; + + if (p_existing!=String() && ResourceCache::has(p_existing)) { + + font = Ref<Font>( ResourceCache::get(p_existing)->cast_to<Font>()); + } + + if (font.is_null()) { + font = Ref<Font>( memnew( Font ) ); + } + + font->clear(); + font->set_height(height+bottom_space+top_space); + font->set_ascent(ascent+top_space); + + //register texures + { + Ref<ImageTexture> t = memnew(ImageTexture); + t->create_from_image(atlas); + t->set_storage( ImageTexture::STORAGE_COMPRESS_LOSSLESS ); + font->add_texture(t); + + } + //register characters + + + for(int i=0;i<font_data_list.size();i++) { + _EditorFontData *fd=font_data_list[i]; + int tex_idx=0; + + font->add_char(fd->character,tex_idx,Rect2( fd->ofs_x, fd->ofs_y, fd->blit.get_width(), fd->blit.get_height()),Point2(fd->halign-fd->blit_ofs.x,fd->valign-fd->blit_ofs.y+top_space), fd->advance+char_space+(fd->character==' '?space_space:0)); + memdelete(fd); + } + + for(Map<_EditorKerningKey,int>::Element *E=kerning_map.front();E;E=E->next()) { + + font->add_kerning_pair(E->key().A,E->key().B,E->get()); + } + + FT_Done_FreeType( library ); + + return font; +#else + + return Ref<Font>(); +#endif +} + + +String EditorFontImportPlugin::get_name() const { + + return "font"; +} +String EditorFontImportPlugin::get_visible_name() const{ + + return "Font"; +} +void EditorFontImportPlugin::import_dialog(const String& p_from){ + + dialog->popup_import(p_from); +} +Error EditorFontImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from){ + + + Ref<Font> font = EditorFontImportPlugin::generate_font(p_from,p_path); + if (!font.is_valid()) + return ERR_CANT_CREATE; + + Ref<ResourceImportMetadata> from=p_from; + from->set_source_md5(0,FileAccess::get_md5(EditorImportPlugin::expand_source_path(from->get_source_path(0)))); + from->set_editor(get_name()); + font->set_import_metadata(from); + + return ResourceSaver::save(p_path,font); + +} + + +EditorFontImportPlugin::EditorFontImportPlugin(EditorNode* p_editor) { + + dialog = memnew( EditorFontImportDialog(this) ); + p_editor->get_gui_base()->add_child(dialog); +} diff --git a/tools/editor/io_plugins/editor_font_import_plugin.h b/tools/editor/io_plugins/editor_font_import_plugin.h new file mode 100644 index 000000000..ac3b4eb0f --- /dev/null +++ b/tools/editor/io_plugins/editor_font_import_plugin.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/* editor_font_import_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_FONT_IMPORT_PLUGIN_H +#define EDITOR_FONT_IMPORT_PLUGIN_H + +#include "tools/editor/editor_import_export.h" +#include "scene/resources/font.h" + +class EditorNode; +class EditorFontImportDialog; + +class EditorFontImportPlugin : public EditorImportPlugin { + + OBJ_TYPE(EditorFontImportPlugin,EditorImportPlugin); + + EditorFontImportDialog *dialog; +public: + + Ref<Font> generate_font(const Ref<ResourceImportMetadata>& p_from,const String& p_existing=String()); //used by editor + + virtual String get_name() const; + virtual String get_visible_name() const; + virtual void import_dialog(const String& p_from=""); + virtual Error import(const String& p_path, const Ref<ResourceImportMetadata>& p_from); + + + EditorFontImportPlugin(EditorNode* p_editor); +}; + +#endif // EDITOR_FONT_IMPORT_PLUGIN_H diff --git a/tools/editor/io_plugins/editor_import_collada.cpp b/tools/editor/io_plugins/editor_import_collada.cpp new file mode 100644 index 000000000..ffb68dff8 --- /dev/null +++ b/tools/editor/io_plugins/editor_import_collada.cpp @@ -0,0 +1,2200 @@ +/*************************************************************************/ +/* editor_import_collada.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "editor_import_collada.h" +#include "collada/collada.h" +#include "scene/3d/spatial.h" +#include "scene/3d/skeleton.h" +#include "scene/3d/path.h" +#include "scene/3d/camera.h" +#include "scene/3d/light.h" +#include "scene/animation/animation_player.h" +#include "scene/3d/mesh_instance.h" +#include "scene/resources/animation.h" +#include "scene/resources/packed_scene.h" +#include "os/os.h" +#include "tools/editor/editor_node.h" + + +struct ColladaImport { + + Collada collada; + Spatial *scene; + + Vector<Ref<Animation> > animations; + + struct NodeMap { + //String path; + Spatial *node; + int bone; + List<int> anim_tracks; + + NodeMap() { node=NULL; bone=-1; } + }; + + bool found_ambient; + Color ambient; + bool found_directional; + bool force_make_tangents; + + + + Map<String,NodeMap> node_map; //map from collada node to engine node + Map<String, Ref<Mesh> > mesh_cache; + Map<String, Ref<Curve3D> > curve_cache; + Map<String, Ref<Material> > material_cache; + + Map< Skeleton*, Map< String, int> > skeleton_bone_map; + + Set<String> valid_animated_nodes; + Vector<int> valid_animated_properties; + Map<String,bool> bones_with_animation; + + Error _populate_skeleton(Skeleton *p_skeleton,Collada::Node *p_node, int &r_bone, int p_parent); + Error _create_scene(Collada::Node *p_node, Spatial *p_parent); + Error _create_resources(Collada::Node *p_node); + Error _create_material(const String& p_material); + Error _create_mesh_surfaces(Ref<Mesh>& p_mesh,const Map<String,Collada::NodeGeometry::Material>& p_material_map,const Collada::MeshData &meshdata,const Transform& p_local_xform,const Vector<int> &bone_remap, const Collada::SkinControllerData *p_skin_data, const Collada::MorphControllerData *p_morph_data); + Error load(const String& p_path, int p_flags, bool p_force_make_tangents=false); + void _fix_param_animation_tracks(); + void create_animation(int p_clip=-1); + void create_animations(); + + Set<String> tracks_in_clips; + Vector<String> missing_textures; + + void _pre_process_lights(Collada::Node *p_node); + + ColladaImport() { + + found_ambient=false; + found_directional=false; + force_make_tangents=false; + + } +}; + + +Error ColladaImport::_populate_skeleton(Skeleton *p_skeleton,Collada::Node *p_node, int &r_bone, int p_parent) { + + + if (p_node->type!=Collada::Node::TYPE_JOINT) + return OK; + + Collada::NodeJoint *joint = static_cast<Collada::NodeJoint*>(p_node); + + p_skeleton->add_bone(p_node->name); + if (p_parent>=0) + p_skeleton->set_bone_parent(r_bone,p_parent); + + NodeMap nm; + nm.node=p_skeleton; + nm.bone = r_bone; + node_map[p_node->id]=nm; + + skeleton_bone_map[p_skeleton][joint->sid]=r_bone; + + if (collada.state.bone_rest_map.has(joint->sid)) { + + p_skeleton->set_bone_rest(r_bone,collada.fix_transform(collada.state.bone_rest_map[joint->sid])); + //should map this bone to something for animation? + } else { + print_line("no rest: "+joint->sid); + WARN_PRINT("Joint has no rest.."); + } + + + int id = r_bone++; + for(int i=0;i<p_node->children.size();i++) { + + Error err = _populate_skeleton(p_skeleton,p_node->children[i],r_bone,id); + if (err) + return err; + } + + return OK; +} + + +void ColladaImport::_pre_process_lights(Collada::Node *p_node) { + + + if (p_node->type==Collada::Node::TYPE_LIGHT) { + + + Collada::NodeLight *light=static_cast<Collada::NodeLight*>(p_node); + if (collada.state.light_data_map.has(light->light)) { + + Collada::LightData &ld = collada.state.light_data_map[light->light]; + if (ld.mode==Collada::LightData::MODE_AMBIENT) { + found_ambient=true; + ambient=ld.color; + } + if (ld.mode==Collada::LightData::MODE_DIRECTIONAL) { + found_directional=true; + } + } + + } + + + for(int i=0;i<p_node->children.size();i++) + _pre_process_lights(p_node->children[i]); +} + +Error ColladaImport::_create_scene(Collada::Node *p_node, Spatial *p_parent) { + + Spatial * node=NULL; + + switch(p_node->type) { + + case Collada::Node::TYPE_NODE: { + + node = memnew( Spatial ); + } break; + case Collada::Node::TYPE_JOINT: { + + return OK; // do nothing + } break; + case Collada::Node::TYPE_LIGHT: { + + //node = memnew( Light) + Collada::NodeLight *light = static_cast<Collada::NodeLight*>(p_node); + if (collada.state.light_data_map.has(light->light)) { + + Collada::LightData &ld = collada.state.light_data_map[light->light]; + + if (ld.mode==Collada::LightData::MODE_AMBIENT) { + + if (found_directional) + return OK; //do nothing not needed + + if (!bool(GLOBAL_DEF("collada/use_ambient",false))) + return OK; + //well, it's an ambient light.. + Light *l = memnew( DirectionalLight ); + l->set_color(Light::COLOR_AMBIENT,ld.color); + l->set_color(Light::COLOR_DIFFUSE,Color(0,0,0)); + l->set_color(Light::COLOR_SPECULAR,Color(0,0,0)); + node = l; + + } else if (ld.mode==Collada::LightData::MODE_DIRECTIONAL) { + + //well, it's an ambient light.. + Light *l = memnew( DirectionalLight ); + if (found_ambient) //use it here + l->set_color(Light::COLOR_AMBIENT,ambient); + + l->set_color(Light::COLOR_DIFFUSE,ld.color); + l->set_color(Light::COLOR_SPECULAR,Color(1,1,1)); + node = l; + } else { + + Light *l; + + if (ld.mode==Collada::LightData::MODE_OMNI) + l=memnew( OmniLight ); + else { + l=memnew( SpotLight ); + l->set_parameter(Light::PARAM_SPOT_ANGLE,ld.spot_angle); + l->set_parameter(Light::PARAM_SPOT_ATTENUATION,ld.spot_exp); + } + + // + l->set_color(Light::COLOR_DIFFUSE,ld.color); + l->set_color(Light::COLOR_SPECULAR,Color(1,1,1)); + l->approximate_opengl_attenuation(ld.constant_att,ld.linear_att,ld.quad_att); + node=l; + } + + } else { + + node = memnew( Spatial ); + } + } break; + case Collada::Node::TYPE_CAMERA: { + + Collada::NodeCamera *cam = static_cast<Collada::NodeCamera*>(p_node); + Camera *camera = memnew( Camera ); + + if (collada.state.camera_data_map.has(cam->camera)) { + + const Collada::CameraData &cd = collada.state.camera_data_map[cam->camera]; + + switch(cd.mode) { + + case Collada::CameraData::MODE_ORTHOGONAL: { + + if (cd.orthogonal.x_mag) { + + camera->set_orthogonal(cd.orthogonal.x_mag,cd.z_near,cd.z_far); + + } else if (!cd.orthogonal.x_mag && cd.orthogonal.y_mag) { + + camera->set_orthogonal(cd.orthogonal.y_mag * cd.aspect,cd.z_near,cd.z_far); + } + + } break; + case Collada::CameraData::MODE_PERSPECTIVE: { + + if (cd.perspective.y_fov) { + + camera->set_perspective(cd.perspective.y_fov,cd.z_near,cd.z_far); + + } else if (!cd.perspective.y_fov && cd.perspective.x_fov) { + + camera->set_perspective(cd.perspective.x_fov / cd.aspect,cd.z_near,cd.z_far); + } + + } break; + } + + } + + node=camera; + + } break; + case Collada::Node::TYPE_GEOMETRY: { + + Collada::NodeGeometry *ng = static_cast<Collada::NodeGeometry*>(p_node); + + if (collada.state.curve_data_map.has(ng->source)) { + + node = memnew( Path ); + } else { + //mesh since nothing else + node = memnew( MeshInstance ); + } + } break; + case Collada::Node::TYPE_SKELETON: { + + Skeleton *sk = memnew( Skeleton ); + int bone = 0; + + for(int i=0;i<p_node->children.size();i++) { + + _populate_skeleton(sk,p_node->children[i],bone,-1); + } + sk->localize_rests(); //after creating skeleton, rests must be localized...! + + node=sk; + } break; + + } + + if (p_node->name!="") + node->set_name(p_node->name); + NodeMap nm; + nm.node=node; + node_map[p_node->id]=nm; + Transform xf = p_node->default_transform; + + xf = collada.fix_transform( xf ) * p_node->post_transform; + node->set_transform(xf); + p_parent->add_child(node); + node->set_owner(scene); + + for(int i=0;i<p_node->children.size();i++) { + + Error err = _create_scene(p_node->children[i],node); + if (err) + return err; + } + return OK; +} + + +Error ColladaImport::_create_material(const String& p_target) { + + ERR_FAIL_COND_V(material_cache.has(p_target),ERR_ALREADY_EXISTS); + ERR_FAIL_COND_V(!collada.state.material_map.has(p_target),ERR_INVALID_PARAMETER); + Collada::Material &src_mat=collada.state.material_map[p_target]; + ERR_FAIL_COND_V(!collada.state.effect_map.has(src_mat.instance_effect),ERR_INVALID_PARAMETER); + Collada::Effect &effect=collada.state.effect_map[src_mat.instance_effect]; + + Ref<FixedMaterial> material= memnew( FixedMaterial ); + + // DIFFUSE + + if (effect.diffuse.texture!="") { + + String texfile = effect.get_texture_path(effect.diffuse.texture,collada); + if (texfile!="") { + + Ref<Texture> texture = ResourceLoader::load(texfile,"Texture"); + if (texture.is_valid()) { + + material->set_texture(FixedMaterial::PARAM_DIFFUSE,texture); + material->set_parameter(FixedMaterial::PARAM_DIFFUSE,Color(1,1,1,1)); + } else { + missing_textures.push_back(texfile.get_file()); + } + } + } else { + material->set_parameter(FixedMaterial::PARAM_DIFFUSE,effect.diffuse.color); + } + + // SPECULAR + + if (effect.specular.texture!="") { + + String texfile = effect.get_texture_path(effect.specular.texture,collada); + if (texfile!="") { + + Ref<Texture> texture = ResourceLoader::load(texfile,"Texture"); + if (texture.is_valid()) { + + material->set_texture(FixedMaterial::PARAM_SPECULAR,texture); + material->set_parameter(FixedMaterial::PARAM_SPECULAR,Color(1,1,1,1)); + } else { + missing_textures.push_back(texfile.get_file()); + } + + } + } else { + material->set_parameter(FixedMaterial::PARAM_SPECULAR,effect.specular.color); + } + + // EMISSION + + if (effect.emission.texture!="") { + + String texfile = effect.get_texture_path(effect.emission.texture,collada); + if (texfile!="") { + + Ref<Texture> texture = ResourceLoader::load(texfile,"Texture"); + if (texture.is_valid()) { + + material->set_texture(FixedMaterial::PARAM_EMISSION,texture); + material->set_parameter(FixedMaterial::PARAM_EMISSION,Color(1,1,1,1)); + }else { + missing_textures.push_back(texfile.get_file()); + } + + } + } else { + material->set_parameter(FixedMaterial::PARAM_EMISSION,effect.emission.color); + } + + // NORMAL + + if (effect.bump.texture!="") { + + String texfile = effect.get_texture_path(effect.bump.texture,collada); + if (texfile!="") { + + Ref<Texture> texture = ResourceLoader::load(texfile,"Texture"); + if (texture.is_valid()) { + + material->set_texture(FixedMaterial::PARAM_NORMAL,texture); + }else { + missing_textures.push_back(texfile.get_file()); + } + + } + } + + + material->set_parameter(FixedMaterial::PARAM_SPECULAR_EXP,effect.shininess); + material->set_flag(Material::FLAG_DOUBLE_SIDED,effect.double_sided); + + material_cache[p_target]=material; + return OK; +} + + +static void _generate_normals(const DVector<int>& p_indices,const DVector<Vector3>& p_vertices,DVector<Vector3>&r_normals) { + + + r_normals.resize(p_vertices.size()); + DVector<Vector3>::Write narrayw = r_normals.write(); + + int iacount=p_indices.size()/3; + DVector<int>::Read index_arrayr = p_indices.read(); + DVector<Vector3>::Read vertex_arrayr = p_vertices.read(); + + for(int idx=0;idx<iacount;idx++) { + + Vector3 v[3]={ + vertex_arrayr[index_arrayr[idx*3+0]], + vertex_arrayr[index_arrayr[idx*3+1]], + vertex_arrayr[index_arrayr[idx*3+2]] + }; + + Vector3 normal = Plane(v[0],v[1],v[2]).normal; + + narrayw[index_arrayr[idx*3+0]]+=normal; + narrayw[index_arrayr[idx*3+1]]+=normal; + narrayw[index_arrayr[idx*3+2]]+=normal; + } + + int vlen=p_vertices.size(); + + for(int idx=0;idx<vlen;idx++) { + narrayw[idx].normalize(); + } + +} + + +static void _generate_tangents_and_binormals(const DVector<int>& p_indices,const DVector<Vector3>& p_vertices,const DVector<Vector3>& p_uvs,const DVector<Vector3>& p_normals,DVector<real_t>&r_tangents) { + + int vlen=p_vertices.size(); + + Vector<Vector3> tangents; + tangents.resize(vlen); + Vector<Vector3> binormals; + binormals.resize(vlen); + + + int iacount=p_indices.size()/3; + + DVector<int>::Read index_arrayr = p_indices.read(); + DVector<Vector3>::Read vertex_arrayr = p_vertices.read(); + DVector<Vector3>::Read narrayr = p_normals.read(); + DVector<Vector3>::Read uvarrayr = p_uvs.read(); + + + for(int idx=0;idx<iacount;idx++) { + + + Vector3 v1 = vertex_arrayr[ index_arrayr[idx*3+0] ]; + Vector3 v2 = vertex_arrayr[ index_arrayr[idx*3+1] ]; + Vector3 v3 = vertex_arrayr[ index_arrayr[idx*3+2] ]; + + Vector3 w1 = uvarrayr[ index_arrayr[idx*3+0] ]; + Vector3 w2 = uvarrayr[ index_arrayr[idx*3+1] ]; + Vector3 w3 = uvarrayr[ index_arrayr[idx*3+2] ]; + + real_t x1 = v2.x - v1.x; + real_t x2 = v3.x - v1.x; + real_t y1 = v2.y - v1.y; + real_t y2 = v3.y - v1.y; + real_t z1 = v2.z - v1.z; + real_t z2 = v3.z - v1.z; + + real_t s1 = w2.x - w1.x; + real_t s2 = w3.x - w1.x; + real_t t1 = w2.y - w1.y; + real_t t2 = w3.y - w1.y; + + real_t r = (s1 * t2 - s2 * t1); + + Vector3 tangent; + Vector3 binormal; + + if (r==0) { + + binormal=Vector3(); + tangent=Vector3(); + } else { + tangent = Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, + (t2 * z1 - t1 * z2) * r); + binormal = Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, + (s1 * z2 - s2 * z1) * r); + } + + tangents[ index_arrayr[idx*3+0] ]+=tangent; + binormals[ index_arrayr[idx*3+0] ]+=binormal; + tangents[ index_arrayr[idx*3+1] ]+=tangent; + binormals[ index_arrayr[idx*3+1] ]+=binormal; + tangents[ index_arrayr[idx*3+2] ]+=tangent; + binormals[ index_arrayr[idx*3+2] ]+=binormal; + + } + + r_tangents.resize(vlen*4); + DVector<real_t>::Write tarrayw = r_tangents.write(); + + for(int idx=0;idx<vlen;idx++) { + Vector3 tangent = tangents[idx]; + Vector3 bingen = narrayr[idx].cross(tangent); + float dir; + if (bingen.dot(binormals[idx]) < 0 ) + dir=-1.0; + else + dir=+1.0; + + tarrayw[idx*4+0]=tangent.x; + tarrayw[idx*4+1]=tangent.y; + tarrayw[idx*4+2]=tangent.z; + tarrayw[idx*4+3]=dir; + } +} + +Error ColladaImport::_create_mesh_surfaces(Ref<Mesh>& p_mesh,const Map<String,Collada::NodeGeometry::Material>& p_material_map,const Collada::MeshData &meshdata,const Transform& p_local_xform,const Vector<int> &bone_remap, const Collada::SkinControllerData *skin_controller, const Collada::MorphControllerData *p_morph_data) { + + + bool local_xform_mirror=p_local_xform.basis.determinant() < 0; + + if (p_morph_data) { + //add morphie target + ERR_FAIL_COND_V( !p_morph_data->targets.has("MORPH_TARGET"), ERR_INVALID_DATA ); + String mt = p_morph_data->targets["MORPH_TARGET"]; + ERR_FAIL_COND_V( !p_morph_data->sources.has(mt), ERR_INVALID_DATA); + int morph_targets = p_morph_data->sources[mt].sarray.size(); + for(int i=0;i<morph_targets;i++) { + + String target = p_morph_data->sources[mt].sarray[i]; + ERR_FAIL_COND_V( !collada.state.mesh_data_map.has(target), ERR_INVALID_DATA ); + String name = collada.state.mesh_data_map[target].name; + + p_mesh->add_morph_target(name); + } + if (p_morph_data->mode=="RELATIVE") + p_mesh->set_morph_target_mode(Mesh::MORPH_MODE_RELATIVE); + else if (p_morph_data->mode=="NORMALIZED") + p_mesh->set_morph_target_mode(Mesh::MORPH_MODE_NORMALIZED); + } + + + int surface=0; + for(int p_i = 0; p_i < meshdata.primitives.size(); p_i ++ ) { + + + + const Collada::MeshData::Primitives& p = meshdata.primitives[p_i]; + + /* VERTEX SOURCE */ + ERR_FAIL_COND_V(!p.sources.has("VERTEX"),ERR_INVALID_DATA); + + String vertex_src_id = p.sources["VERTEX"].source; + int vertex_ofs=p.sources["VERTEX"].offset; + + ERR_FAIL_COND_V(!meshdata.vertices.has(vertex_src_id),ERR_INVALID_DATA); + + ERR_FAIL_COND_V(!meshdata.vertices[vertex_src_id].sources.has("POSITION"),ERR_INVALID_DATA); + String position_src_id = meshdata.vertices[vertex_src_id].sources["POSITION"]; + + ERR_FAIL_COND_V(!meshdata.sources.has(position_src_id),ERR_INVALID_DATA); + + const Collada::MeshData::Source *vertex_src=&meshdata.sources[position_src_id]; + + /* NORMAL SOURCE */ + + const Collada::MeshData::Source *normal_src=NULL; + int normal_ofs=0; + + if (p.sources.has("NORMAL")) { + + String normal_source_id = p.sources["NORMAL"].source; + normal_ofs = p.sources["NORMAL"].offset; + ERR_FAIL_COND_V( !meshdata.sources.has(normal_source_id),ERR_INVALID_DATA); + normal_src=&meshdata.sources[normal_source_id]; + } + + const Collada::MeshData::Source *binormal_src=NULL; + int binormal_ofs=0; + + if (p.sources.has("TEXBINORMAL")) { + + String binormal_source_id = p.sources["TEXBINORMAL"].source; + binormal_ofs = p.sources["TEXBINORMAL"].offset; + ERR_FAIL_COND_V( !meshdata.sources.has(binormal_source_id),ERR_INVALID_DATA); + binormal_src=&meshdata.sources[binormal_source_id]; + } + + const Collada::MeshData::Source *tangent_src=NULL; + int tangent_ofs=0; + + if (p.sources.has("TEXTANGENT")) { + + String tangent_source_id = p.sources["TEXTANGENT"].source; + tangent_ofs = p.sources["TEXTANGENT"].offset; + ERR_FAIL_COND_V( !meshdata.sources.has(tangent_source_id),ERR_INVALID_DATA); + tangent_src=&meshdata.sources[tangent_source_id]; + } + + + const Collada::MeshData::Source *uv_src=NULL; + int uv_ofs=0; + + if (p.sources.has("TEXCOORD0")) { + + String uv_source_id = p.sources["TEXCOORD0"].source; + uv_ofs = p.sources["TEXCOORD0"].offset; + ERR_FAIL_COND_V( !meshdata.sources.has(uv_source_id),ERR_INVALID_DATA); + uv_src=&meshdata.sources[uv_source_id]; + } + + const Collada::MeshData::Source *uv2_src=NULL; + int uv2_ofs=0; + + if (p.sources.has("TEXCOORD1")) { + + String uv2_source_id = p.sources["TEXCOORD1"].source; + uv2_ofs = p.sources["TEXCOORD1"].offset; + ERR_FAIL_COND_V( !meshdata.sources.has(uv2_source_id),ERR_INVALID_DATA); + uv2_src=&meshdata.sources[uv2_source_id]; + } + + + const Collada::MeshData::Source *color_src=NULL; + int color_ofs=0; + + if (false && p.sources.has("COLOR")) { + + String color_source_id = p.sources["COLOR"].source; + color_ofs = p.sources["COLOR"].offset; + ERR_FAIL_COND_V( !meshdata.sources.has(color_source_id), ERR_INVALID_DATA ); + color_src=&meshdata.sources[color_source_id]; + } + + //find largest source.. + + + Set<Collada::Vertex> vertex_set; //vertex set will be the vertices + List<int> indices_list; //indices will be the indices + Map<int,Set<int> > vertex_map; //map vertices (for setting skinning/morph) + + /**************************/ + /* CREATE PRIMITIVE ARRAY */ + /**************************/ + + // The way collada uses indices is more optimal, and friendlier with 3D modelling sofware, + // because it can index everything, not only vertices (similar to how the WII works). + // This is, however, more incompatible with standard video cards, so arrays must be converted. + // Must convert to GL/DX format. + + int _prim_ofs=0; + for(int p_i=0;p_i<p.count;p_i++) { + + + int amount; + if (p.polygons.size()) { + + ERR_FAIL_INDEX_V(p_i,p.polygons.size(),ERR_INVALID_DATA); + amount=p.polygons[p_i]; + } else { + amount=3; //triangles; + } + + //COLLADA_PRINT("amount: "+itos(amount)); + + int prev2[2]={0,0}; + + for(int j=0;j<amount;j++) { + + int src=_prim_ofs; + //_prim_ofs+=p.sources.size() + + ERR_FAIL_INDEX_V(src,p.indices.size(),ERR_INVALID_DATA); + + Collada::Vertex vertex; + + int vertex_index=p.indices[src+vertex_ofs]; //used for index field (later used by controllers) + int vertex_pos = (vertex_src->stride?vertex_src->stride:3) * vertex_index; + ERR_FAIL_INDEX_V(vertex_pos,vertex_src->array.size(),ERR_INVALID_DATA); + vertex.vertex=Vector3(vertex_src->array[vertex_pos+0],vertex_src->array[vertex_pos+1],vertex_src->array[vertex_pos+2]); + + + if (normal_src) { + + + + int normal_pos = (normal_src->stride?normal_src->stride:3) * p.indices[src+normal_ofs]; + ERR_FAIL_INDEX_V(normal_pos,normal_src->array.size(),ERR_INVALID_DATA); + vertex.normal=Vector3(normal_src->array[normal_pos+0],normal_src->array[normal_pos+1],normal_src->array[normal_pos+2]); + vertex.normal=vertex.normal.snapped(0.001); + + + if (tangent_src && binormal_src) { + + int binormal_pos = (binormal_src->stride?binormal_src->stride:3) * p.indices[src+binormal_ofs]; + ERR_FAIL_INDEX_V(binormal_pos,binormal_src->array.size(),ERR_INVALID_DATA); + Vector3 binormal =Vector3(binormal_src->array[binormal_pos+0],binormal_src->array[binormal_pos+1],binormal_src->array[binormal_pos+2]); + + int tangent_pos = (tangent_src->stride?tangent_src->stride:3) * p.indices[src+tangent_ofs]; + ERR_FAIL_INDEX_V(tangent_pos,tangent_src->array.size(),ERR_INVALID_DATA); + Vector3 tangent =Vector3(tangent_src->array[tangent_pos+0],tangent_src->array[tangent_pos+1],tangent_src->array[tangent_pos+2]); + + vertex.tangent.normal=tangent; + vertex.tangent.d= vertex.normal.cross(tangent).dot(binormal) > 0 ? -1 : 1; + } + + } + + + if (uv_src) { + + int uv_pos = (uv_src->stride?uv_src->stride:2) * p.indices[src+uv_ofs]; + ERR_FAIL_INDEX_V(uv_pos,uv_src->array.size(),ERR_INVALID_DATA); + vertex.uv=Vector3(uv_src->array[uv_pos+0],1.0-uv_src->array[uv_pos+1],0); + } + + if (uv2_src) { + + int uv2_pos = (uv2_src->stride?uv2_src->stride:2) * p.indices[src+uv2_ofs]; + ERR_FAIL_INDEX_V(uv2_pos,uv2_src->array.size(),ERR_INVALID_DATA); + vertex.uv2=Vector3(uv2_src->array[uv2_pos+0],1.0-uv2_src->array[uv2_pos+1],0); + } + + if (color_src) { + + int color_pos = (color_src->stride?color_src->stride:3) * p.indices[src+color_ofs]; // colors are RGB in collada.. + ERR_FAIL_INDEX_V(color_pos,color_src->array.size(),ERR_INVALID_DATA); + vertex.color=Color(color_src->array[color_pos+0],color_src->array[color_pos+1],color_src->array[color_pos+2],(color_src->stride>3)?color_src->array[color_pos+3]:1.0); + + } + +#ifndef NO_UP_AXIS_SWAP + if (collada.state.up_axis==Vector3::AXIS_Z) { + + SWAP( vertex.vertex.z, vertex.vertex.y ); + vertex.vertex.z = -vertex.vertex.z; + SWAP( vertex.normal.z, vertex.normal.y ); + vertex.normal.z = -vertex.normal.z; + + } + +#endif + + vertex.fix_unit_scale(collada); + int index=0; + //COLLADA_PRINT("vertex: "+vertex.vertex); + + if (vertex_set.has(vertex)) { + + index=vertex_set.find(vertex)->get().idx; + } else { + + index=vertex_set.size(); + vertex.idx=index; + vertex_set.insert(vertex); + } + + if (!vertex_map.has(vertex_index)) + vertex_map[vertex_index]=Set<int>(); + vertex_map[vertex_index].insert(index); //should be outside.. + //build triangles if needed + if (j==0) + prev2[0]=index; + + if (j>=2) { + //insert indices in reverse order (collada uses CCW as frontface) + if (local_xform_mirror) { + + indices_list.push_back(prev2[0]); + indices_list.push_back(prev2[1]); + indices_list.push_back(index); + + } else { + indices_list.push_back(prev2[0]); + indices_list.push_back(index); + indices_list.push_back(prev2[1]); + } + } + + prev2[1]=index; + _prim_ofs+=p.vertex_size; + } + + } + + + + Vector<Collada::Vertex> vertex_array; //there we go, vertex array + + vertex_array.resize(vertex_set.size()); + for(Set<Collada::Vertex>::Element *F=vertex_set.front();F;F=F->next()) { + + vertex_array[F->get().idx]=F->get(); + } + + /************************/ + /* ADD WEIGHTS IF EXIST */ + /************************/ + + + bool has_weights=false; + + if (skin_controller) { + + const Collada::SkinControllerData::Source *weight_src=NULL; + int weight_ofs=0; + + if (skin_controller->weights.sources.has("WEIGHT")) { + + String weight_id = skin_controller->weights.sources["WEIGHT"].source; + weight_ofs = skin_controller->weights.sources["WEIGHT"].offset; + if (skin_controller->sources.has(weight_id)) { + + weight_src = &skin_controller->sources[weight_id]; + + } + } + + int joint_ofs=0; + + if (skin_controller->weights.sources.has("JOINT")) { + + joint_ofs = skin_controller->weights.sources["JOINT"].offset; + } + + //should be OK, given this was pre-checked. + + int index_ofs=0; + int wstride = skin_controller->weights.sources.size(); + for(int w_i=0;w_i<skin_controller->weights.sets.size();w_i++) { + + int amount = skin_controller->weights.sets[w_i]; + + if (vertex_map.has(w_i)) { //vertex may no longer be here, don't bother converting + + Vector<Collada::Vertex::Weight> weights; + + for (int a_i=0;a_i<amount;a_i++) { + + Collada::Vertex::Weight w; + + int read_from = index_ofs+a_i*wstride; + ERR_FAIL_INDEX_V(read_from+wstride-1,skin_controller->weights.indices.size(),ERR_INVALID_DATA); + int weight_index = skin_controller->weights.indices[read_from+weight_ofs]; + ERR_FAIL_INDEX_V(weight_index,weight_src->array.size(),ERR_INVALID_DATA); + + w.weight = weight_src->array[weight_index]; + + int bone_index = skin_controller->weights.indices[read_from+joint_ofs]; + if (bone_index==-1) + continue; //ignore this weight (refers to bind shape) + ERR_FAIL_INDEX_V(bone_index,bone_remap.size(),ERR_INVALID_DATA); + + w.bone_idx=bone_remap[bone_index]; + + + weights.push_back(w); + } + + /* FIX WEIGHTS */ + + + + weights.sort(); + + if (weights.size()>4) { + //cap to 4 and make weights add up 1 + weights.resize(4); + + } + + //make sure weights allways add up to 1 + float total=0; + for(int i=0;i<weights.size();i++) + total+=weights[i].weight; + if (total) + for(int i=0;i<weights.size();i++) + weights[i].weight/=total; + + if (weights.size()==0 || total==0) { //if nothing, add a weight to bone 0 + //no weights assigned + Collada::Vertex::Weight w; + w.bone_idx=0; + w.weight=1.0; + weights.clear(); + weights.push_back(w); + + } + + + for(Set<int>::Element *E=vertex_map[w_i].front();E;E=E->next()) { + + int dst = E->get(); + ERR_EXPLAIN("invalid vertex index in array"); + ERR_FAIL_INDEX_V(dst,vertex_array.size(),ERR_INVALID_DATA); + vertex_array[dst].weights=weights; + + } + + } else { + //zzprint_line("no vertex found for index "+itos(w_i)); + } + + index_ofs+=wstride*amount; + + } + + //vertices need to be localized + + Transform local_xform = p_local_xform; + for(int i=0;i<vertex_array.size();i++) { + + vertex_array[i].vertex=local_xform.xform(vertex_array[i].vertex); + vertex_array[i].normal=local_xform.basis.xform(vertex_array[i].normal).normalized(); + vertex_array[i].tangent.normal=local_xform.basis.xform(vertex_array[i].tangent.normal).normalized(); + if (local_xform_mirror) { + //i shouldn't do this? wtf? + //vertex_array[i].normal*=-1.0; + //vertex_array[i].tangent.normal*=-1.0; + } + } + + has_weights=true; + + } + + DVector<int> index_array; + index_array.resize(indices_list.size()); + DVector<int>::Write index_arrayw = index_array.write(); + + int iidx=0; + for(List<int>::Element *F=indices_list.front();F;F=F->next()) { + + index_arrayw[iidx++]=F->get(); + } + + index_arrayw=DVector<int>::Write(); + + + /*****************/ + /* MAKE SURFACES */ + /*****************/ + + + { + + Ref<FixedMaterial> material; + + //find material + Mesh::PrimitiveType primitive=Mesh::PRIMITIVE_TRIANGLES; + + { + + if (p_material_map.has(p.material)) { + String target=p_material_map[p.material].target; + + if (!material_cache.has(target)) { + Error err = _create_material(target); + if (!err) + material=material_cache[target]; + } else + material=material_cache[target]; + + } else if (p.material!=""){ + print_line("Warning, unreferenced material in geometry instance: "+p.material); + } + } + + + + DVector<Vector3> final_vertex_array; + DVector<Vector3> final_normal_array; + DVector<float> final_tangent_array; + DVector<Color> final_color_array; + DVector<Vector3> final_uv_array; + DVector<Vector3> final_uv2_array; + DVector<float> final_bone_array; + DVector<float> final_weight_array; + + uint32_t final_format=0; + + //create format + final_format=Mesh::ARRAY_FORMAT_VERTEX|Mesh::ARRAY_FORMAT_INDEX; + + if (normal_src) { + final_format|=Mesh::ARRAY_FORMAT_NORMAL; + if (uv_src && binormal_src && tangent_src) { + final_format|=Mesh::ARRAY_FORMAT_TANGENT; + } + + } + + + + if (color_src) + final_format|=Mesh::ARRAY_FORMAT_COLOR; + if (uv_src) + final_format|=Mesh::ARRAY_FORMAT_TEX_UV; + if (uv2_src) + final_format|=Mesh::ARRAY_FORMAT_TEX_UV2; + + if (has_weights) { + final_format|=Mesh::ARRAY_FORMAT_WEIGHTS; + final_format|=Mesh::ARRAY_FORMAT_BONES; + } + + + //set arrays + + int vlen = vertex_array.size(); + { //vertices + + DVector<Vector3> varray; + varray.resize(vertex_array.size()); + + DVector<Vector3>::Write varrayw = varray.write(); + + for(int k=0;k<vlen;k++) + varrayw[k]=vertex_array[k].vertex; + + varrayw = DVector<Vector3>::Write(); + final_vertex_array=varray; + + } + + + if (uv_src) { //compute uv first, may be needed for computing tangent/bionrmal + DVector<Vector3> uvarray; + uvarray.resize(vertex_array.size()); + DVector<Vector3>::Write uvarrayw = uvarray.write(); + + for(int k=0;k<vlen;k++) { + uvarrayw[k]=vertex_array[k].uv; + } + + uvarrayw = DVector<Vector3>::Write(); + final_uv_array=uvarray; + + } + + if (uv2_src) { //compute uv first, may be needed for computing tangent/bionrmal + DVector<Vector3> uv2array; + uv2array.resize(vertex_array.size()); + DVector<Vector3>::Write uv2arrayw = uv2array.write(); + + for(int k=0;k<vlen;k++) { + uv2arrayw[k]=vertex_array[k].uv; + } + + uv2arrayw = DVector<Vector3>::Write(); + final_uv2_array=uv2array; + + } + + if (normal_src) { + DVector<Vector3> narray; + narray.resize(vertex_array.size()); + DVector<Vector3>::Write narrayw = narray.write(); + + for(int k=0;k<vlen;k++) { + narrayw[k]=vertex_array[k].normal; + } + + narrayw = DVector<Vector3>::Write(); + final_normal_array=narray; + + //DVector<Vector3> altnaray; + //_generate_normals(index_array,final_vertex_array,altnaray); + + //for(int i=0;i<altnaray.size();i++) + // print_line(rtos(altnaray[i].dot(final_normal_array[i]))); + + } else if (primitive==Mesh::PRIMITIVE_TRIANGLES) { + //generate normals (even if unused later) + + _generate_normals(index_array,final_vertex_array,final_normal_array); + if (OS::get_singleton()->is_stdout_verbose()) + print_line("Collada: Triangle mesh lacks normals, so normals were generated."); + final_format|=Mesh::ARRAY_FORMAT_NORMAL; + + } + + if (final_normal_array.size() && uv_src && binormal_src && tangent_src && !force_make_tangents) { + + DVector<real_t> tarray; + tarray.resize(vertex_array.size()*4); + DVector<real_t>::Write tarrayw = tarray.write(); + + + for(int k=0;k<vlen;k++) { + tarrayw[k*4+0]=vertex_array[k].tangent.normal.x; + tarrayw[k*4+1]=vertex_array[k].tangent.normal.y; + tarrayw[k*4+2]=vertex_array[k].tangent.normal.z; + tarrayw[k*4+3]=vertex_array[k].tangent.d; + + } + + tarrayw = DVector<real_t>::Write(); + + final_tangent_array=tarray; + } else if (final_normal_array.size() && primitive==Mesh::PRIMITIVE_TRIANGLES && final_uv_array.size() && (force_make_tangents || (material.is_valid() && material->get_texture(FixedMaterial::PARAM_NORMAL).is_valid()))){ + //if this uses triangles, there are uvs and the material is using a normalmap, generate tangents and binormals, because they WILL be needed + //generate binormals/tangents + _generate_tangents_and_binormals(index_array,final_vertex_array,final_uv_array,final_normal_array,final_tangent_array); + final_format|=Mesh::ARRAY_FORMAT_TANGENT; + if (OS::get_singleton()->is_stdout_verbose()) + print_line("Collada: Triangle mesh lacks tangents (And normalmap was used), so tangents were generated."); + + } + + + if (color_src) { + DVector<Color> colorarray; + colorarray.resize(vertex_array.size()); + DVector<Color>::Write colorarrayw = colorarray.write(); + + for(int k=0;k<vlen;k++) { + colorarrayw[k]=vertex_array[k].color; + } + + colorarrayw = DVector<Color>::Write(); + + final_color_array=colorarray; + } + + if (has_weights) { + DVector<float> weightarray; + DVector<float> bonearray; + + weightarray.resize(vertex_array.size()*4); + DVector<float>::Write weightarrayw = weightarray.write(); + bonearray.resize(vertex_array.size()*4); + DVector<float>::Write bonearrayw = bonearray.write(); + + for(int k=0;k<vlen;k++) { + float sum=0; + + for(int l=0;l<VS::ARRAY_WEIGHTS_SIZE;l++) { + if (l<vertex_array[k].weights.size()) { + weightarrayw[k*VS::ARRAY_WEIGHTS_SIZE+l]=vertex_array[k].weights[l].weight; + sum+=weightarrayw[k*VS::ARRAY_WEIGHTS_SIZE+l]; + bonearrayw[k*VS::ARRAY_WEIGHTS_SIZE+l]=vertex_array[k].weights[l].bone_idx; + //COLLADA_PRINT(itos(k)+": "+rtos(bonearrayw[k*VS::ARRAY_WEIGHTS_SIZE+l])+":"+rtos(weightarray[k*VS::ARRAY_WEIGHTS_SIZE+l])); + } else { + + weightarrayw[k*VS::ARRAY_WEIGHTS_SIZE+l]=0; + bonearrayw[k*VS::ARRAY_WEIGHTS_SIZE+l]=0; + + } + + + } +// if (sum<0.8) +// COLLADA_PRINT("ERROR SUMMING INDEX "+itos(k)+" had weights: "+itos(vertex_array[k].weights.size())); + + } + + weightarrayw = DVector<float>::Write(); + bonearrayw = DVector<float>::Write(); + + final_weight_array = weightarray; + final_bone_array = bonearray; + } + + + + //////////////////////////// + // FINALLY CREATE SUFRACE // + //////////////////////////// + + Array d; + d.resize(VS::ARRAY_MAX); + + d[Mesh::ARRAY_INDEX]=index_array; + d[Mesh::ARRAY_VERTEX]=final_vertex_array; + + if (final_normal_array.size()) + d[Mesh::ARRAY_NORMAL]=final_normal_array; + if (final_tangent_array.size()) + d[Mesh::ARRAY_TANGENT]=final_tangent_array; + if (final_uv_array.size()) + d[Mesh::ARRAY_TEX_UV]=final_uv_array; + if (final_uv2_array.size()) + d[Mesh::ARRAY_TEX_UV2]=final_uv2_array; + if (final_color_array.size()) + d[Mesh::ARRAY_COLOR]=final_color_array; + if (final_weight_array.size()) + d[Mesh::ARRAY_WEIGHTS]=final_weight_array; + if (final_bone_array.size()) + d[Mesh::ARRAY_BONES]=final_bone_array; + + + Array mr; + + //////////////////////////// + // THEN THE MORPH TARGETS // + //////////////////////////// + + if (p_morph_data) { + + //add morphie target + ERR_FAIL_COND_V( !p_morph_data->targets.has("MORPH_TARGET"), ERR_INVALID_DATA ); + String mt = p_morph_data->targets["MORPH_TARGET"]; + ERR_FAIL_COND_V( !p_morph_data->sources.has(mt), ERR_INVALID_DATA); + int morph_targets = p_morph_data->sources[mt].sarray.size(); + mr.resize(morph_targets); + + for(int j=0;j<morph_targets;j++) { + + Array mrt; + mrt.resize(VS::ARRAY_MAX); + + String target = p_morph_data->sources[mt].sarray[j]; + ERR_FAIL_COND_V( !collada.state.mesh_data_map.has(target), ERR_INVALID_DATA ); + String name = collada.state.mesh_data_map[target].name; + Collada::MeshData &md = collada.state.mesh_data_map[target]; + + // collada in itself supports morphing everything. However, the spec is unclear and no examples or exporters that + // morph anything but "POSITIONS" seem to exit. Because of this, normals and binormals/tangents have to be regenerated here, + // which may result in inaccurate (but most of the time good enough) results. + + DVector<Vector3> vertices; + vertices.resize(vlen); + + ERR_FAIL_COND_V( md.vertices.size() != 1, ERR_INVALID_DATA); + String vertex_src_id=md.vertices.front()->key(); + ERR_FAIL_COND_V(!md.vertices[vertex_src_id].sources.has("POSITION"),ERR_INVALID_DATA); + String position_src_id = md.vertices[vertex_src_id].sources["POSITION"]; + + ERR_FAIL_COND_V(!md.sources.has(position_src_id),ERR_INVALID_DATA); + + const Collada::MeshData::Source *m=&md.sources[position_src_id]; + + ERR_FAIL_COND_V( m->array.size() != vertex_src->array.size(), ERR_INVALID_DATA); + int stride=m->stride; + if (stride==0) + stride=3; + + + //read vertices from morph target + DVector<Vector3>::Write vertw = vertices.write(); + + for(int m_i=0;m_i<m->array.size()/stride;m_i++) { + + int pos = m_i*stride; + Vector3 vtx( m->array[pos+0], m->array[pos+1], m->array[pos+2] ); + +#ifndef NO_UP_AXIS_SWAP + if (collada.state.up_axis==Vector3::AXIS_Z) { + + SWAP( vtx.z, vtx.y ); + vtx.z = -vtx.z; + + } +#endif + + Collada::Vertex vertex; + vertex.vertex=vtx; + vertex.fix_unit_scale(collada); + vtx=vertex.vertex; + + vtx = p_local_xform.xform(vtx); + + + if (vertex_map.has(m_i)) { //vertex may no longer be here, don't bother converting + + + for (Set<int> ::Element *E=vertex_map[m_i].front() ; E; E=E->next() ) { + + vertw[E->get()]=vtx; + } + } + } + + + //vertices are in place, now generate everything else + vertw = DVector<Vector3>::Write(); + DVector<Vector3> normals; + DVector<float> tangents; + + _generate_normals(index_array,vertices,normals); + if (final_tangent_array.size() && final_uv_array.size()) { + + _generate_tangents_and_binormals(index_array,vertices,final_uv_array,normals,tangents); + + } + + mrt[Mesh::ARRAY_VERTEX]=vertices; + + mrt[Mesh::ARRAY_NORMAL]=normals; + if (tangents.size()) + mrt[Mesh::ARRAY_TANGENT]=tangents; + if (final_uv_array.size()) + mrt[Mesh::ARRAY_TEX_UV]=final_uv_array; + if (final_uv2_array.size()) + mrt[Mesh::ARRAY_TEX_UV2]=final_uv2_array; + if (final_color_array.size()) + mrt[Mesh::ARRAY_COLOR]=final_color_array; + + mr[j]=mrt; + + } + + } + + + p_mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES,d,mr); + + if (material.is_valid()) { + p_mesh->surface_set_material(surface, material); + p_mesh->surface_set_name(surface, material->get_name()); + } + } + + /*****************/ + /* FIND MATERIAL */ + /*****************/ + + surface++; + } + + + return OK; + +} + + +Error ColladaImport::_create_resources(Collada::Node *p_node) { + + + if (p_node->type==Collada::Node::TYPE_GEOMETRY && node_map.has(p_node->id)) { + + + Spatial * node=node_map[p_node->id].node; + Collada::NodeGeometry *ng = static_cast<Collada::NodeGeometry*>(p_node); + + + if (node->cast_to<Path>()) { + + Path *path = node->cast_to<Path>(); + + String curve = ng->source; + + if (curve_cache.has(ng->source)) { + + path->set_curve(curve_cache[ng->source]); + } else { + + Ref<Curve3D> c = memnew( Curve3D ); + + const Collada::CurveData &cd = collada.state.curve_data_map[ng->source]; + + ERR_FAIL_COND_V( !cd.control_vertices.has("POSITION") , ERR_INVALID_DATA); + ERR_FAIL_COND_V( !cd.control_vertices.has("IN_TANGENT") , ERR_INVALID_DATA); + ERR_FAIL_COND_V( !cd.control_vertices.has("OUT_TANGENT") , ERR_INVALID_DATA); + ERR_FAIL_COND_V( !cd.control_vertices.has("INTERPOLATION") , ERR_INVALID_DATA); + + + ERR_FAIL_COND_V( !cd.sources.has(cd.control_vertices["POSITION"] ) , ERR_INVALID_DATA); + const Collada::CurveData::Source &vertices = cd.sources[ cd.control_vertices["POSITION"] ]; + ERR_FAIL_COND_V( vertices.stride!=3, ERR_INVALID_DATA ); + + ERR_FAIL_COND_V( !cd.sources.has(cd.control_vertices["IN_TANGENT"] ) , ERR_INVALID_DATA); + const Collada::CurveData::Source &in_tangents = cd.sources[ cd.control_vertices["IN_TANGENT"] ]; + ERR_FAIL_COND_V( in_tangents.stride!=3 , ERR_INVALID_DATA); + + ERR_FAIL_COND_V( !cd.sources.has(cd.control_vertices["OUT_TANGENT"] ), ERR_INVALID_DATA ); + const Collada::CurveData::Source &out_tangents = cd.sources[ cd.control_vertices["OUT_TANGENT"] ]; + ERR_FAIL_COND_V( out_tangents.stride!=3, ERR_INVALID_DATA ); + + ERR_FAIL_COND_V( !cd.sources.has(cd.control_vertices["INTERPOLATION"] ), ERR_INVALID_DATA ); + const Collada::CurveData::Source &interps = cd.sources[ cd.control_vertices["INTERPOLATION"] ]; + ERR_FAIL_COND_V( interps.stride!=1, ERR_INVALID_DATA ); + + const Collada::CurveData::Source *tilts=NULL; + if (cd.control_vertices.has("TILT") && cd.sources.has(cd.control_vertices["TILT"])) + tilts=&cd.sources[ cd.control_vertices["TILT"] ]; + + + if (tilts) { + print_line("FOUND TILTS!!!"); + } + int pc = vertices.array.size()/3; + for(int i=0;i<pc;i++) { + + Vector3 pos( vertices.array[i*3+0], vertices.array[i*3+1], vertices.array[i*3+2] ); + Vector3 in( in_tangents.array[i*3+0], in_tangents.array[i*3+1], in_tangents.array[i*3+2] ); + Vector3 out( out_tangents.array[i*3+0], out_tangents.array[i*3+1], out_tangents.array[i*3+2] ); + +#ifndef NO_UP_AXIS_SWAP + if (collada.state.up_axis==Vector3::AXIS_Z) { + + SWAP(pos.y,pos.z); + pos.z=-pos.z; + SWAP(in.y,in.z); + in.z=-in.z; + SWAP(out.y,out.z); + out.z=-out.z; + } +#endif + pos*=collada.state.unit_scale; + in*=collada.state.unit_scale; + out*=collada.state.unit_scale; + + c->add_point(pos,in-pos,out-pos); + if (tilts) + c->set_point_tilt(i,tilts->array[i]); + + } + + curve_cache[ng->source]=c; + path->set_curve(c); + + } + + + } + + + if (node->cast_to<MeshInstance>()) { + + + Collada::NodeGeometry *ng = static_cast<Collada::NodeGeometry*>(p_node); + + MeshInstance *mi = node->cast_to<MeshInstance>(); + + + ERR_FAIL_COND_V(!mi,ERR_BUG); + + + Collada::SkinControllerData *skin=NULL; + Collada::MorphControllerData *morph=NULL; + String meshid; + Transform apply_xform; + Vector<int> bone_remap; + + if (ng->controller) { + + if (collada.state.skin_controller_data_map.has(ng->source)) { + + + ERR_FAIL_COND_V(!collada.state.skin_controller_data_map.has(ng->source),ERR_INVALID_DATA); + skin=&collada.state.skin_controller_data_map[ng->source]; + + Vector<String> skeletons = ng->skeletons; + + ERR_FAIL_COND_V( skeletons.empty(), ERR_INVALID_DATA ); + + String skname = skeletons[0]; + ERR_FAIL_COND_V( !node_map.has(skname), ERR_INVALID_DATA ); + NodeMap nmsk = node_map[skname]; + Skeleton *sk = nmsk.node->cast_to<Skeleton>(); + ERR_FAIL_COND_V( !sk, ERR_INVALID_DATA ); + ERR_FAIL_COND_V( !skeleton_bone_map.has(sk), ERR_INVALID_DATA ); + Map<String, int> &bone_remap_map=skeleton_bone_map[sk]; + + + meshid=skin->base; + + if (collada.state.morph_controller_data_map.has(meshid)) { + //it's a morph!! + morph = &collada.state.morph_controller_data_map[meshid]; + meshid=morph->mesh; + } + + apply_xform=collada.fix_transform(p_node->default_transform); + node->set_transform(Transform()); + + Collada::SkinControllerData::Source *joint_src=NULL; + + ERR_FAIL_COND_V(!skin->weights.sources.has("JOINT"),ERR_INVALID_DATA); + + String joint_id = skin->weights.sources["JOINT"].source; + ERR_FAIL_COND_V(!skin->sources.has(joint_id),ERR_INVALID_DATA); + + joint_src = &skin->sources[joint_id]; + + bone_remap.resize(joint_src->sarray.size()); + + for(int i=0;i<bone_remap.size();i++) { + + String str = joint_src->sarray[i]; + ERR_FAIL_COND_V( !bone_remap_map.has(str), ERR_INVALID_DATA ); + bone_remap[i]=bone_remap_map[str]; + } + } else if (collada.state.morph_controller_data_map.has(ng->source)) { + //it's a morph!! + morph = &collada.state.morph_controller_data_map[meshid]; + meshid=morph->mesh; + } else { + ERR_EXPLAIN("Controller Instance Source '"+ng->source+"' is neither skin or morph!"); + ERR_FAIL_V( ERR_INVALID_DATA ); + } + + + + } else { + meshid=ng->source; + } + + Ref<Mesh> mesh; + if (mesh_cache.has(meshid)) { + mesh=mesh_cache[meshid]; + } else { + if (collada.state.mesh_data_map.has(meshid)) { + //bleh, must ignore invalid + + ERR_FAIL_COND_V(!collada.state.mesh_data_map.has(meshid),ERR_INVALID_DATA); + mesh=Ref<Mesh>(memnew( Mesh )); + const Collada::MeshData &meshdata = collada.state.mesh_data_map[meshid]; + mesh->set_name( meshdata.name ); + Error err = _create_mesh_surfaces(mesh,ng->material_map,meshdata,apply_xform,bone_remap,skin,morph); + ERR_FAIL_COND_V(err,err); + + mesh_cache[meshid]=mesh; + } else { + + print_line("Warning, will not import geometry: "+meshid); + } + } + + if (!mesh.is_null()) { + mi->set_mesh(mesh); + } + } + } + + for(int i=0;i<p_node->children.size();i++) { + + Error err = _create_resources(p_node->children[i]); + if (err) + return err; + } + return OK; +} + + +Error ColladaImport::load(const String& p_path,int p_flags,bool p_force_make_tangents) { + + Error err = collada.load(p_path,p_flags); + ERR_FAIL_COND_V(err,err); + + force_make_tangents=p_force_make_tangents; + ERR_FAIL_COND_V( !collada.state.visual_scene_map.has( collada.state.root_visual_scene ), ERR_INVALID_DATA ); + Collada::VisualScene &vs = collada.state.visual_scene_map[ collada.state.root_visual_scene ]; + + scene = memnew( Spatial ); // root + + //determine what's going on with the lights + for(int i=0;i<vs.root_nodes.size();i++) { + + _pre_process_lights(vs.root_nodes[i]); + + } + //import scene + for(int i=0;i<vs.root_nodes.size();i++) { + + Error err = _create_scene(vs.root_nodes[i],scene); + if (err!=OK) { + memdelete(scene); + ERR_FAIL_COND_V(err,err); + } + + Error err2 = _create_resources(vs.root_nodes[i]); + if (err2!=OK) { + memdelete(scene); + ERR_FAIL_COND_V(err2,err2); + } + } + + //optatively, set unit scale in the root + scene->set_transform(collada.get_root_transform()); + + + return OK; + +} + +void ColladaImport::_fix_param_animation_tracks() { + + for (Map<String,Collada::Node*>::Element *E=collada.state.scene_map.front();E;E=E->next()) { + + Collada::Node *n = E->get(); + switch(n->type) { + + case Collada::Node::TYPE_NODE: { + // ? do nothing + } break; + case Collada::Node::TYPE_JOINT: { + + } break; + case Collada::Node::TYPE_SKELETON: { + + } break; + case Collada::Node::TYPE_LIGHT: { + + } break; + case Collada::Node::TYPE_CAMERA: { + + } break; + case Collada::Node::TYPE_GEOMETRY: { + + Collada::NodeGeometry *ng = static_cast<Collada::NodeGeometry*>(n); + // test source(s) + String source = ng->source; + + while (source!="") { + + if (collada.state.skin_controller_data_map.has(source)) { + + const Collada::SkinControllerData& skin = collada.state.skin_controller_data_map[source]; + + //nothing to animate here i think + + source=skin.base; + } else if (collada.state.morph_controller_data_map.has(source)) { + + const Collada::MorphControllerData& morph = collada.state.morph_controller_data_map[source]; + if (morph.targets.has("MORPH_WEIGHT") && morph.targets.has("MORPH_TARGET")) { + + String weights = morph.targets["MORPH_WEIGHT"]; + String targets = morph.targets["MORPH_TARGET"]; + + if (morph.sources.has(targets) && morph.sources.has(weights)) { + const Collada::MorphControllerData::Source &weight_src=morph.sources[weights]; + const Collada::MorphControllerData::Source &target_src=morph.sources[targets]; + + ERR_FAIL_COND(weight_src.array.size() != target_src.sarray.size()); + + for(int i=0;i<weight_src.array.size();i++) { + + String track_name = weights+"("+itos(i)+")"; + String mesh_name = target_src.sarray[i]; + if (collada.state.mesh_name_map.has(mesh_name) && collada.state.referenced_tracks.has(track_name)) { + + const Vector<int>&rt = collada.state.referenced_tracks[track_name]; + + for(int rti=0;rti<rt.size();rti++) { + Collada::AnimationTrack *at = &collada.state.animation_tracks[rt[rti]]; + + at->target=E->key(); + at->param="morph/"+collada.state.mesh_name_map[mesh_name]; + at->property=true; + //at->param + } + } + } + } + } + source=morph.mesh; + } else { + + source=""; // for now nothing else supported + } + } + + } break; + + } + } + +} + +void ColladaImport::create_animations() { + + print_line("-=-=-=-=-PRE CA"); + _fix_param_animation_tracks(); + for(int i=0;i<collada.state.animation_clips.size();i++) { + + for(int j=0;j<collada.state.animation_clips[i].tracks.size();j++) + tracks_in_clips.insert(collada.state.animation_clips[i].tracks[j]); + } + + + + for(int i=0;i<collada.state.animation_tracks.size();i++) { + + Collada::AnimationTrack &at = collada.state.animation_tracks[i]; + if (!node_map.has(at.target)) { + print_line("Coudlnt find node: "+at.target); + continue; + } + + + if (at.property) { + + valid_animated_properties.push_back(i); + + } else { + node_map[at.target].anim_tracks.push_back(i); + valid_animated_nodes.insert(at.target); + } + + } + + create_animation(); + print_line("clipcount: "+itos(collada.state.animation_clips.size())); + for(int i=0;i<collada.state.animation_clips.size();i++) + create_animation(i); + +} + +void ColladaImport::create_animation(int p_clip) { + + Ref<Animation> animation = Ref<Animation>( memnew( Animation )); + + if (p_clip==-1) { + + print_line("default"); + animation->set_name("default"); + } else { + print_line("clip name: "+collada.state.animation_clips[p_clip].name); + animation->set_name(collada.state.animation_clips[p_clip].name); + } + + for(Map<String,NodeMap>::Element *E=node_map.front();E;E=E->next()) { + + if (E->get().bone<0) + continue; + bones_with_animation[E->key()]=false; + } + //store and validate tracks + + if (p_clip==-1) { + //main anim + } + + Set<int> track_filter; + + + if (p_clip==-1) { + + for(int i=0;i<collada.state.animation_clips.size();i++) { + + int tc = collada.state.animation_clips[i].tracks.size(); + for(int j=0;j<tc;j++) { + + String n = collada.state.animation_clips[i].tracks[j]; + if (collada.state.by_id_tracks.has(n)) { + + const Vector<int>&ti = collada.state.by_id_tracks[n]; + for(int k=0;k<ti.size();k++) { + track_filter.insert(ti[k]); + } + } + } + } + } else { + + int tc = collada.state.animation_clips[p_clip].tracks.size(); + for(int j=0;j<tc;j++) { + + String n = collada.state.animation_clips[p_clip].tracks[j]; + if (collada.state.by_id_tracks.has(n)) { + + const Vector<int>&ti = collada.state.by_id_tracks[n]; + for(int k=0;k<ti.size();k++) { + track_filter.insert(ti[k]); + } + } + } + + } + + //animation->set_loop(true); + //create animation tracks + + Vector<float> base_snapshots; + + float f=0; + float snapshot_interval = 1.0/20.0; //should be customizable somewhere... + + float anim_length=collada.state.animation_length; + if (p_clip>=0 && collada.state.animation_clips[p_clip].end) + anim_length=collada.state.animation_clips[p_clip].end; + + while(f<collada.state.animation_length) { + if (f>=collada.state.animation_length) + f=collada.state.animation_length; + + base_snapshots.push_back(f); + f+=snapshot_interval; + } + print_line("anim len: "+rtos(anim_length)); + animation->set_length(anim_length); + + bool tracks_found=false; + + for(Set<String>::Element* E=valid_animated_nodes.front();E;E=E->next()) { + + // take snapshots + if (!collada.state.scene_map.has(E->get())) + continue; + + NodeMap &nm = node_map[E->get()]; + String path = scene->get_path_to(nm.node); + + if (nm.bone>=0) { + Skeleton *sk = static_cast<Skeleton*>(nm.node); + String name = sk->get_bone_name(nm.bone); + path=path+":"+name; + } + + bool found_anim=false; + + + Collada::Node *cn = collada.state.scene_map[E->get()]; + if (cn->ignore_anim) { + print_line("warning, ignoring animation on node: "+path); + continue; + } + + + + animation->add_track(Animation::TYPE_TRANSFORM); + int track = animation->get_track_count() -1; + animation->track_set_path( track , path ); + + Vector<float> snapshots = base_snapshots; + + if (nm.anim_tracks.size()==1) { + //use snapshot keys from anim track instead, because this was most likely exported baked + Collada::AnimationTrack &at = collada.state.animation_tracks[nm.anim_tracks.front()->get()]; + snapshots.clear(); + for(int i=0;i<at.keys.size();i++) + snapshots.push_back(at.keys[i].time); + + } + + + for(int i=0;i<snapshots.size();i++) { + + for(List<int>::Element *ET=nm.anim_tracks.front();ET;ET=ET->next()) { + //apply tracks + + if (p_clip==-1) { + + if (track_filter.has(ET->get())) + continue; + } else { + + if (!track_filter.has(ET->get())) + continue; + + } + + found_anim=true; + + Collada::AnimationTrack &at = collada.state.animation_tracks[ET->get()]; + + int xform_idx=-1; + for(int j=0;j<cn->xform_list.size();j++) { + + + if (cn->xform_list[j].id==at.param) { + + xform_idx=j; + break; + } + } + + if (xform_idx==-1) { + print_line("couldnt find matching node "+at.target+" xform for track "+at.param); + continue; + } + + ERR_CONTINUE(xform_idx==-1); + + Vector<float> data = at.get_value_at_time(snapshots[i]); + ERR_CONTINUE(data.empty()); + + + Collada::Node::XForm &xf = cn->xform_list[xform_idx]; + + if (at.component=="ANGLE") { + ERR_CONTINUE(data.size()!=1); + ERR_CONTINUE(xf.op!=Collada::Node::XForm::OP_ROTATE); + ERR_CONTINUE(xf.data.size()<4); + xf.data[3]=data[0]; + } else if (at.component=="X" || at.component=="Y" || at.component=="Z") { + int cn=at.component[0]-'X'; + ERR_CONTINUE(cn>=xf.data.size()); + ERR_CONTINUE(data.size()>1); + xf.data[cn]=data[0]; + } else if (data.size()==xf.data.size()) { + + xf.data=data; + } else { + + + if ( data.size()!=xf.data.size() ) { + print_line("component "+at.component+" datasize "+itos(data.size())+" xfdatasize "+itos(xf.data.size())); + } + + ERR_CONTINUE( data.size()!=xf.data.size() ); + } + } + + Transform xform = cn->compute_transform(collada); + xform = collada.fix_transform(xform) * cn->post_transform; + + + if (nm.bone>=0) { + //make bone transform relative to rest (in case of skeleton) + Skeleton *sk = nm.node->cast_to<Skeleton>(); + if (sk) { + + xform = sk->get_bone_rest(nm.bone).affine_inverse() * xform; + } else { + + ERR_PRINT("INVALID SKELETON!!!!"); + } + } + + Quat q = xform.basis; + q.normalize(); + Vector3 s = xform.basis.get_scale(); + Vector3 l = xform.origin; + + animation->transform_track_insert_key(track,snapshots[i],l,q,s); + + } + + + + if (nm.bone>=0) { + if (found_anim) + bones_with_animation[E->get()]=true; + } + + if (found_anim) + tracks_found=true; + else { + animation->remove_track( track ); + } + + } + + + //some bones may lack animation, but since we don't store pose as a property, we must add keyframes! + for(Map<String,bool>::Element *E=bones_with_animation.front();E;E=E->next()) { + + if (E->get()) + continue; + + //print_line("BONE LACKS ANIM: "+E->key()); + + NodeMap &nm = node_map[E->key()]; + String path = scene->get_path_to(nm.node); + ERR_CONTINUE( nm.bone <0 ); + Skeleton *sk = static_cast<Skeleton*>(nm.node); + String name = sk->get_bone_name(nm.bone); + path=path+":"+name; + + Collada::Node *cn = collada.state.scene_map[E->key()]; + if (cn->ignore_anim) { + print_line("warning, ignoring animation on node: "+path); + continue; + } + + animation->add_track(Animation::TYPE_TRANSFORM); + int track = animation->get_track_count() -1; + animation->track_set_path( track , path ); + + + Transform xform = cn->compute_transform(collada); + xform = collada.fix_transform(xform) * cn->post_transform; + + xform = sk->get_bone_rest(nm.bone).affine_inverse() * xform; + + Quat q = xform.basis; + q.normalize(); + Vector3 s = xform.basis.get_scale(); + Vector3 l = xform.origin; + + animation->transform_track_insert_key(track,0,l,q,s); + + tracks_found=true; + } + + + + + for(int i=0;i<valid_animated_properties.size();i++) { + + + int ti = valid_animated_properties[i]; + + if (p_clip==-1) { + + if (track_filter.has(ti)) + continue; + } else { + + if (!track_filter.has(ti)) + continue; + + } + + + Collada::AnimationTrack &at = collada.state.animation_tracks[ ti ]; + + // take snapshots + if (!collada.state.scene_map.has(at.target)) + continue; + + NodeMap &nm = node_map[at.target]; + String path = scene->get_path_to(nm.node); + + Collada::Node *cn = collada.state.scene_map[at.target]; + //if (cn->ignore_anim) { + // print_line("warning, ignoring property animation on node: "+nm.path); + //continue; + //} + + animation->add_track(Animation::TYPE_VALUE); + int track = animation->get_track_count() -1; + path = path +":"+at.param; + animation->track_set_path( track , path ); + + for(int i=0;i<at.keys.size();i++) { + + float time = at.keys[i].time; + Variant value; + Vector<float> data = at.keys[i].data; + if (data.size()==1) { + //push a float + value=data[0]; + + } else if (data.size()==16) { + //matrix + print_line("value keys for matrices not supported"); + } else { + + print_line("don't know what to do with this amount of value keys: "+itos(data.size())); + } + + animation->track_insert_key(track,time,value); + } + + + tracks_found=true; + + } + + + + if (tracks_found) { + + animations.push_back(animation); + } + + + +} + + +/*********************************************************************************/ +/*************************************** SCENE ***********************************/ +/*********************************************************************************/ + + +#define DEBUG_ANIMATION + + +uint32_t EditorSceneImporterCollada::get_import_flags() const { + + return IMPORT_SCENE|IMPORT_ANIMATION; + +} +void EditorSceneImporterCollada::get_extensions(List<String> *r_extensions) const { + + + r_extensions->push_back("dae"); +} +Node* EditorSceneImporterCollada::import_scene(const String& p_path,uint32_t p_flags,Error* r_err) { + + + ColladaImport state; + uint32_t flags=Collada::IMPORT_FLAG_SCENE; + if (p_flags&IMPORT_ANIMATION) + flags|=Collada::IMPORT_FLAG_ANIMATION; + + + Error err = state.load(p_path,flags,p_flags&EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS); + + ERR_FAIL_COND_V(err!=OK,NULL); + + if (state.missing_textures.size()) { + + for(int i=0;i<state.missing_textures.size();i++) { + EditorNode::add_io_error("Texture Not Found: "+state.missing_textures[i]); + } + + if (p_flags&IMPORT_FAIL_ON_MISSING_DEPENDENCIES) + return NULL; + } + + if (p_flags&IMPORT_ANIMATION) { + + state.create_animations(); + AnimationPlayer *ap = memnew( AnimationPlayer ); + for(int i=0;i<state.animations.size();i++) { + String name; + if (state.animations[i]->get_name()=="") + name="default"; + else + name=state.animations[i]->get_name(); + + if (p_flags&IMPORT_ANIMATION_OPTIMIZE) + state.animations[i]->optimize(); + if (p_flags&IMPORT_ANIMATION_DETECT_LOOP) { + + if (name.begins_with("loop") || name.ends_with("loop") || name.begins_with("cycle") || name.ends_with("cycle")) { + state.animations[i]->set_loop(true); + } + } + + ap->add_animation(name,state.animations[i]); + } + state.scene->add_child(ap); + ap->set_owner(state.scene); + + } + + return state.scene; + +} + +Ref<Animation> EditorSceneImporterCollada::import_animation(const String& p_path,uint32_t p_flags) { + + + ColladaImport state; + + + Error err = state.load(p_path,Collada::IMPORT_FLAG_ANIMATION,p_flags&EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS); + ERR_FAIL_COND_V(err!=OK,RES()); + + + state.create_animations(); + if (state.scene) + memdelete(state.scene); + + if (state.animations.size()==0) + return Ref<Animation>(); + Ref<Animation> anim=state.animations[0]; + anim=state.animations[0]; + print_line("Anim Load OK"); + String base = p_path.basename().to_lower(); + if (p_flags&IMPORT_ANIMATION_DETECT_LOOP) { + + if (base.begins_with("loop") || base.ends_with("loop") || base.begins_with("cycle") || base.ends_with("cycle")) { + anim->set_loop(true); + } + } + + if (p_flags&IMPORT_ANIMATION_OPTIMIZE) + anim->optimize(); + + return anim; +} + + +EditorSceneImporterCollada::EditorSceneImporterCollada() { + + +} diff --git a/tools/editor/io_plugins/editor_import_collada.h b/tools/editor/io_plugins/editor_import_collada.h new file mode 100644 index 000000000..239f05119 --- /dev/null +++ b/tools/editor/io_plugins/editor_import_collada.h @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* editor_import_collada.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_IMPORT_COLLADA_H +#define EDITOR_IMPORT_COLLADA_H + +#include "tools/editor/io_plugins/editor_scene_import_plugin.h" + + + +class EditorSceneImporterCollada : public EditorSceneImporter { + + OBJ_TYPE(EditorSceneImporterCollada,EditorSceneImporter ); +public: + + virtual uint32_t get_import_flags() const; + virtual void get_extensions(List<String> *r_extensions) const; + virtual Node* import_scene(const String& p_path,uint32_t p_flags,Error* r_err=NULL); + virtual Ref<Animation> import_animation(const String& p_path,uint32_t p_flags); + + EditorSceneImporterCollada(); +}; + + +#endif + diff --git a/tools/editor/io_plugins/editor_sample_import_plugin.cpp b/tools/editor/io_plugins/editor_sample_import_plugin.cpp new file mode 100644 index 000000000..55cba432e --- /dev/null +++ b/tools/editor/io_plugins/editor_sample_import_plugin.cpp @@ -0,0 +1,630 @@ +/*************************************************************************/ +/* editor_sample_import_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "editor_sample_import_plugin.h" +#include "scene/gui/file_dialog.h" +#include "tools/editor/editor_dir_dialog.h" +#include "tools/editor/editor_node.h" +#include "tools/editor/property_editor.h" +#include "scene/resources/sample.h" +#include "io/resource_saver.h" +#include "os/file_access.h" +#include "io/marshalls.h" + +class _EditorSampleImportOptions : public Object { + + OBJ_TYPE(_EditorSampleImportOptions,Object); +public: + + enum CompressBitrate { + COMPRESS_64, + COMPRESS_96, + COMPRESS_128, + COMPRESS_192 + }; + + bool force_8_bit; + bool force_mono; + bool force_rate; + float force_rate_hz; + + bool edit_trim; + bool edit_normalize; + bool edit_loop; + + bool compress_enable; + CompressBitrate compress_bitrate; + + + bool _set(const StringName& p_name, const Variant& p_value) { + + String n = p_name; + if (n=="force/8_bit") + force_8_bit=p_value; + else if (n=="force/mono") + force_mono=p_value; + else if (n=="force/max_rate") + force_rate=p_value; + else if (n=="force/max_rate_hz") + force_rate_hz=p_value; + else if (n=="edit/trim") + edit_trim=p_value; + else if (n=="edit/normalize") + edit_normalize=p_value; + else if (n=="edit/loop") + edit_loop=p_value; + else if (n=="compress/enable") + compress_enable=p_value; + else if (n=="compress/bitrate") + compress_bitrate=CompressBitrate(int(p_value)); + else + return false; + + return true; + + } + + bool _get(const StringName& p_name,Variant &r_ret) const{ + + String n = p_name; + if (n=="force/8_bit") + r_ret=force_8_bit; + else if (n=="force/mono") + r_ret=force_mono; + else if (n=="force/max_rate") + r_ret=force_rate; + else if (n=="force/max_rate_hz") + r_ret=force_rate_hz; + else if (n=="edit/trim") + r_ret=edit_trim; + else if (n=="edit/normalize") + r_ret=edit_normalize; + else if (n=="edit/loop") + r_ret=edit_loop; + else if (n=="compress/enable") + r_ret=compress_enable; + else if (n=="compress/bitrate") + r_ret=compress_bitrate; + else + return false; + + return true; + + } + void _get_property_list( List<PropertyInfo> *p_list) const{ + + p_list->push_back(PropertyInfo(Variant::BOOL,"force/8_bit")); + p_list->push_back(PropertyInfo(Variant::BOOL,"force/mono")); + p_list->push_back(PropertyInfo(Variant::BOOL,"force/max_rate")); + p_list->push_back(PropertyInfo(Variant::REAL,"force/max_rate_hz",PROPERTY_HINT_EXP_RANGE,"11025,192000,1")); + p_list->push_back(PropertyInfo(Variant::BOOL,"edit/trim")); + p_list->push_back(PropertyInfo(Variant::BOOL,"edit/normalize")); + p_list->push_back(PropertyInfo(Variant::BOOL,"edit/loop")); + //p_list->push_back(PropertyInfo(Variant::BOOL,"compress/enable")); + //p_list->push_back(PropertyInfo(Variant::INT,"compress/bitrate",PROPERTY_HINT_ENUM,"64,96,128,192")); + + + } + + + static void _bind_methods() { + + + ADD_SIGNAL( MethodInfo("changed")); + } + + + _EditorSampleImportOptions() { + + force_8_bit=false; + force_mono=false; + force_rate=true; + force_rate_hz=44100; + + edit_trim=true; + edit_normalize=true; + edit_loop=false; + + compress_enable=false; + compress_bitrate=COMPRESS_128; + } + + +}; + +class EditorSampleImportDialog : public ConfirmationDialog { + + OBJ_TYPE(EditorSampleImportDialog,ConfirmationDialog); + + EditorSampleImportPlugin *plugin; + + LineEdit *import_path; + LineEdit *save_path; + FileDialog *file_select; + EditorDirDialog *save_select; + ConfirmationDialog *error_dialog; + PropertyEditor *option_editor; + + _EditorSampleImportOptions *options; + + +public: + + void _choose_files(const Vector<String>& p_path) { + + String files; + for(int i=0;i<p_path.size();i++) { + + if (i>0) + files+=","; + files+=p_path[i]; + } + /* + if (p_path.size()) { + String srctex=p_path[0]; + String ipath = EditorImportDB::get_singleton()->find_source_path(srctex); + + if (ipath!="") + save_path->set_text(ipath.get_base_dir()); + }*/ + import_path->set_text(files); + + } + void _choose_save_dir(const String& p_path) { + + save_path->set_text(p_path); + } + + void _browse() { + + file_select->popup_centered_ratio(); + } + + void _browse_target() { + + save_select->popup_centered_ratio(); + + } + + + void popup_import(const String& p_path) { + + popup_centered(Size2(400,400)); + if (p_path!="") { + + Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path); + ERR_FAIL_COND(!rimd.is_valid()); + + save_path->set_text(p_path.get_base_dir()); + List<String> opts; + rimd->get_options(&opts); + for(List<String>::Element *E=opts.front();E;E=E->next()) { + + options->_set(E->get(),rimd->get_option(E->get())); + } + + String src = ""; + for(int i=0;i<rimd->get_source_count();i++) { + if (i>0) + src+=","; + src+=EditorImportPlugin::expand_source_path(rimd->get_source_path(i)); + } + import_path->set_text(src); + } + } + + + void _import() { + + Vector<String> samples = import_path->get_text().split(","); + + if (samples.size()==0) { + error_dialog->set_text("No samples to import!"); + error_dialog->popup_centered(Size2(200,100)); + } + + for(int i=0;i<samples.size();i++) { + + Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata ); + + List<PropertyInfo> pl; + options->_get_property_list(&pl); + for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) { + + Variant v; + String opt=E->get().name; + options->_get(opt,v); + imd->set_option(opt,v); + + } + + imd->add_source(EditorImportPlugin::validate_source_path(samples[i])); + + String dst = save_path->get_text(); + if (dst=="") { + error_dialog->set_text("Save path is empty!"); + error_dialog->popup_centered(Size2(200,100)); + } + + dst = dst.plus_file(samples[i].get_file().basename()+".smp"); + + Error err = plugin->import(dst,imd); + } + + hide(); + + } + + + void _notification(int p_what) { + + + if (p_what==NOTIFICATION_ENTER_SCENE) { + + option_editor->edit(options); + } + } + + static void _bind_methods() { + + + ObjectTypeDB::bind_method("_choose_files",&EditorSampleImportDialog::_choose_files); + ObjectTypeDB::bind_method("_choose_save_dir",&EditorSampleImportDialog::_choose_save_dir); + ObjectTypeDB::bind_method("_import",&EditorSampleImportDialog::_import); + ObjectTypeDB::bind_method("_browse",&EditorSampleImportDialog::_browse); + ObjectTypeDB::bind_method("_browse_target",&EditorSampleImportDialog::_browse_target); + // ADD_SIGNAL( MethodInfo("imported",PropertyInfo(Variant::OBJECT,"scene")) ); + } + + EditorSampleImportDialog(EditorSampleImportPlugin *p_plugin) { + + plugin=p_plugin; + + + set_title("Import Audio Samples"); + + VBoxContainer *vbc = memnew( VBoxContainer ); + add_child(vbc); + set_child_rect(vbc); + + + HBoxContainer *hbc = memnew( HBoxContainer ); + vbc->add_margin_child("Source Sample(s):",hbc); + + import_path = memnew( LineEdit ); + import_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(import_path); + + Button * import_choose = memnew( Button ); + import_choose->set_text(" .. "); + hbc->add_child(import_choose); + + import_choose->connect("pressed", this,"_browse"); + + hbc = memnew( HBoxContainer ); + vbc->add_margin_child("Target Path:",hbc); + + save_path = memnew( LineEdit ); + save_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(save_path); + + Button * save_choose = memnew( Button ); + save_choose->set_text(" .. "); + hbc->add_child(save_choose); + + save_choose->connect("pressed", this,"_browse_target"); + + file_select = memnew(FileDialog); + file_select->set_access(FileDialog::ACCESS_FILESYSTEM); + add_child(file_select); + file_select->set_mode(FileDialog::MODE_OPEN_FILES); + file_select->connect("files_selected", this,"_choose_files"); + file_select->add_filter("*.wav ; MS Waveform"); + save_select = memnew( EditorDirDialog ); + add_child(save_select); + + // save_select->set_mode(FileDialog::MODE_OPEN_DIR); + save_select->connect("dir_selected", this,"_choose_save_dir"); + + get_ok()->connect("pressed", this,"_import"); + get_ok()->set_text("Import"); + + + error_dialog = memnew ( ConfirmationDialog ); + add_child(error_dialog); + error_dialog->get_ok()->set_text("Accept"); + // error_dialog->get_cancel()->hide(); + + set_hide_on_ok(false); + options = memnew( _EditorSampleImportOptions ); + + option_editor = memnew( PropertyEditor ); + option_editor->hide_top_label(); + vbc->add_margin_child("Options:",option_editor,true); + } + + ~EditorSampleImportDialog() { + memdelete(options); + } + +}; + + +String EditorSampleImportPlugin::get_name() const { + + return "sample"; +} +String EditorSampleImportPlugin::get_visible_name() const{ + + return "Audio Sample"; +} +void EditorSampleImportPlugin::import_dialog(const String& p_from){ + + dialog->popup_import(p_from); +} +Error EditorSampleImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from){ + + ERR_FAIL_COND_V(p_from->get_source_count()!=1,ERR_INVALID_PARAMETER); + + Ref<ResourceImportMetadata> from=p_from; + + String src_path=EditorImportPlugin::expand_source_path(from->get_source_path(0)); + Ref<Sample> smp = ResourceLoader::load(src_path); + ERR_FAIL_COND_V(smp.is_null(),ERR_CANT_OPEN); + + + float rate = smp->get_mix_rate(); + bool is16 = smp->get_format()==Sample::FORMAT_PCM16; + int chans = smp->is_stereo()?2:1; + int len = smp->get_length(); + Sample::LoopFormat loop= smp->get_loop_format(); + int loop_beg = smp->get_loop_begin(); + int loop_end = smp->get_loop_end(); + + print_line("Input Sample: "); + print_line("\tlen: "+itos(len)); + print_line("\tchans: "+itos(chans)); + print_line("\t16bits: "+itos(is16)); + print_line("\trate: "+itos(rate)); + print_line("\tloop: "+itos(loop)); + print_line("\tloop begin: "+itos(loop_beg)); + print_line("\tloop end: "+itos(loop_end)); + Vector<float> data; + data.resize(len*chans); + + { + DVector<uint8_t> src_data = smp->get_data(); + DVector<uint8_t>::Read sr = src_data.read(); + + + for(int i=0;i<len*chans;i++) { + + float s=0; + if (is16) { + + int16_t i16 = decode_uint16(&sr[i*2]); + s=i16/32767.0; + } else { + + int8_t i8 = sr[i]; + s=i8/127.0; + } + data[i]=s; + } + } + + //apply frequency limit + + bool limit_rate = from->get_option("force/max_rate"); + int limit_rate_hz = from->get_option("force/max_rate_hz"); + if (limit_rate && rate > limit_rate_hz) { + //resampleeee!!! + int new_data_len = len * limit_rate_hz / rate; + Vector<float> new_data; + new_data.resize( new_data_len * chans ); + for(int c=0;c<chans;c++) { + + for(int i=0;i<new_data_len;i++) { + + //simple cubic interpolation should be enough. + float pos = float(i) * len / new_data_len; + float mu = pos-Math::floor(pos); + int ipos = int(Math::floor(pos)); + + float y0=data[MAX(0,ipos-i)]; + float y1=data[ipos]; + float y2=data[MIN(len-1,ipos+1)]; + float y3=data[MIN(len-1,ipos+2)]; + + float mu2 = mu*mu; + float a0 = y3 - y2 - y0 + y1; + float a1 = y0 - y1 - a0; + float a2 = y2 - y0; + float a3 = y1; + + float res=(a0*mu*mu2+a1*mu2+a2*mu+a3); + + new_data[i*chans+c]=res; + } + } + + if (loop) { + + loop_beg=loop_beg*new_data_len/len; + loop_end=loop_end*new_data_len/len; + } + data=new_data; + rate=limit_rate_hz; + len=new_data_len; + } + + + bool normalize = from->get_option("edit/normalize"); + + if (normalize) { + + float max=0; + for(int i=0;i<data.size();i++) { + + float amp = Math::abs(data[i]); + if (amp>max) + max=amp; + } + + if (max>0) { + + float mult=1.0/max; + for(int i=0;i<data.size();i++) { + + data[i]*=mult; + } + + } + } + + bool trim = from->get_option("edit/trim"); + + if (trim && !loop) { + + int first=0; + int last=(len*chans)-1; + bool found=false; + float limit = Math::db2linear(-30); + for(int i=0;i<data.size();i++) { + float amp = Math::abs(data[i]); + + if (!found && amp > limit) { + first=i; + found=true; + } + + if (found && amp > limit) { + last=i; + } + } + + first/=chans; + last/=chans; + + if (first<last) { + + Vector<float> new_data; + new_data.resize((last-first+1)*chans); + for(int i=first*chans;i<=last*chans;i++) { + new_data[i-first*chans]=data[i]; + } + + data=new_data; + len=data.size()/chans; + } + + } + + bool make_loop = from->get_option("edit/loop"); + + if (make_loop && !loop) { + + loop=Sample::LOOP_FORWARD; + loop_beg=0; + loop_end=len; + } + + bool force_mono = from->get_option("force/mono"); + if (force_mono && chans==2) { + + Vector<float> new_data; + new_data.resize(data.size()/2); + for(int i=0;i<len;i++) { + new_data[i]=(data[i*2+0]+data[i*2+1])/2.0; + } + + data=new_data; + chans=1; + } + + bool force_8_bit = from->get_option("force/8_bit"); + if (force_8_bit) { + + is16=false; + } + + + DVector<uint8_t> dst_data; + dst_data.resize( data.size() * (is16?2:1)); + { + DVector<uint8_t>::Write w = dst_data.write(); + + int ds=data.size(); + for(int i=0;i<ds;i++) { + + if (is16) { + int16_t v = CLAMP(data[i]*32767,-32768,32767); + encode_uint16(v,&w[i*2]); + } else { + int8_t v = CLAMP(data[i]*127,-128,127); + w[i]=v; + } + } + } + + + Ref<Sample> target; + + if (ResourceCache::has(p_path)) { + + target = Ref<Sample>( ResourceCache::get(p_path)->cast_to<Sample>() ); + } else { + + target = smp; + } + + target->create(is16?Sample::FORMAT_PCM16:Sample::FORMAT_PCM8,chans==2?true:false,len); + target->set_data(dst_data); + target->set_mix_rate(rate); + target->set_loop_format(loop); + target->set_loop_begin(loop_beg); + target->set_loop_end(loop_end); + + from->set_source_md5(0,FileAccess::get_md5(src_path)); + from->set_editor(get_name()); + target->set_import_metadata(from); + + + Error err = ResourceSaver::save(p_path,smp); + + return err; + +} + + +EditorSampleImportPlugin::EditorSampleImportPlugin(EditorNode* p_editor) { + + dialog = memnew( EditorSampleImportDialog(this)); + p_editor->get_gui_base()->add_child(dialog); +} + diff --git a/tools/editor/io_plugins/editor_sample_import_plugin.h b/tools/editor/io_plugins/editor_sample_import_plugin.h new file mode 100644 index 000000000..a5420671e --- /dev/null +++ b/tools/editor/io_plugins/editor_sample_import_plugin.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* editor_sample_import_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_SAMPLE_IMPORT_PLUGIN_H +#define EDITOR_SAMPLE_IMPORT_PLUGIN_H + +#include "tools/editor/editor_import_export.h" +#include "scene/resources/font.h" + +class EditorNode; +class EditorSampleImportDialog; + +class EditorSampleImportPlugin : public EditorImportPlugin { + + OBJ_TYPE(EditorSampleImportPlugin,EditorImportPlugin); + + EditorSampleImportDialog *dialog; +public: + + virtual String get_name() const; + virtual String get_visible_name() const; + virtual void import_dialog(const String& p_from=""); + virtual Error import(const String& p_path, const Ref<ResourceImportMetadata>& p_from); + + + EditorSampleImportPlugin(EditorNode* p_editor); +}; + +#endif // EDITOR_SAMPLE_IMPORT_PLUGIN_H diff --git a/tools/editor/io_plugins/editor_scene_import_plugin.cpp b/tools/editor/io_plugins/editor_scene_import_plugin.cpp new file mode 100644 index 000000000..e7e3a4206 --- /dev/null +++ b/tools/editor/io_plugins/editor_scene_import_plugin.cpp @@ -0,0 +1,1599 @@ +/*************************************************************************/ +/* editor_scene_import_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "editor_scene_import_plugin.h" +#include "globals.h" +#include "tools/editor/editor_node.h" +#include "scene/resources/packed_scene.h" +#include "os/file_access.h" +#include "scene/3d/path.h" +#include "scene/animation/animation_player.h" +#include "io/resource_saver.h" +#include "scene/3d/mesh_instance.h" +#include "scene/3d/room_instance.h" +#include "scene/3d/portal.h" + + + + + +EditorSceneImporter::EditorSceneImporter() { + + +} + +void EditorScenePostImport::_bind_methods() { + + BIND_VMETHOD( MethodInfo("post_import",PropertyInfo(Variant::OBJECT,"scene")) ); + +} + +Error EditorScenePostImport::post_import(Node* p_scene) { + + if (get_script_instance()) + return Error(int(get_script_instance()->call("post_import",p_scene))); + return OK; +} + +EditorScenePostImport::EditorScenePostImport() { + + +} + + +///////////////////////////// + + +class EditorImportAnimationOptions : public VBoxContainer { + + OBJ_TYPE( EditorImportAnimationOptions, VBoxContainer ); + + + Tree *flags; + Vector<TreeItem*> items; + + bool updating; + + void _changed(); +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + + void set_flags(uint32_t p_flags); + uint32_t get_flags() const; + + + EditorImportAnimationOptions(); + + +}; + +//////////////////////////// + +class EditorSceneImportDialog : public ConfirmationDialog { + + OBJ_TYPE(EditorSceneImportDialog,ConfirmationDialog); + + + EditorImportTextureOptions *texture_options; + EditorImportAnimationOptions *animation_options; + + EditorSceneImportPlugin *plugin; + + EditorNode *editor; + + LineEdit *import_path; + LineEdit *save_path; + LineEdit *script_path; + Tree *import_options; + FileDialog *file_select; + FileDialog *script_select; + EditorDirDialog *save_select; + OptionButton *texture_action; + + Vector<TreeItem*> scene_flags; + + Map<Ref<Mesh>,Ref<Shape> > collision_map; + ConfirmationDialog *error_dialog; + + void _choose_file(const String& p_path); + void _choose_save_file(const String& p_path); + void _choose_script(const String& p_path); + void _browse(); + void _browse_target(); + void _browse_script(); + void _import(); + + +protected: + + void _notification(int p_what); + static void _bind_methods(); +public: + + Error import(const String& p_from, const String& p_to, const String& p_preset); + void popup_import(const String& p_from); + EditorSceneImportDialog(EditorNode *p_editor,EditorSceneImportPlugin *p_plugin); +}; + +/////////////////////////////////// + + +static const char *anim_flag_names[]={ + "Detect Loop", + "Keep Value Tracks", + "Optimize", + NULL +}; + +static const char *anim_flag_descript[]={ + "Set loop flag for animation names that\ncontain 'cycle' or 'loop' in the name.", + "When merging an existing aimation,\nkeep the user-created value-tracks.", + "Remove redundant keyframes in\n transform tacks.", + NULL +}; + + + +void EditorImportAnimationOptions::set_flags(uint32_t p_flags){ + + updating=true; + for(int i=0;i<items.size();i++) { + + items[i]->set_checked(0,p_flags&(1<<i)); + } + updating=false; + +} +uint32_t EditorImportAnimationOptions::get_flags() const{ + + uint32_t f=0; + for(int i=0;i<items.size();i++) { + + if (items[i]->is_checked(0)) + f|=(1<<i); + } + + return f; +} + + +void EditorImportAnimationOptions::_changed() { + + if (updating) + return; + emit_signal("changed"); +} + + +void EditorImportAnimationOptions::_bind_methods() { + + ObjectTypeDB::bind_method("_changed",&EditorImportAnimationOptions::_changed); +// ObjectTypeDB::bind_method("_changedp",&EditorImportAnimationOptions::_changedp); + + ADD_SIGNAL(MethodInfo("changed")); +} + + +void EditorImportAnimationOptions::_notification(int p_what) { + + if (p_what==NOTIFICATION_ENTER_SCENE) { + + flags->connect("item_edited",this,"_changed"); +// format->connect("item_selected",this,"_changedp"); + } +} + +EditorImportAnimationOptions::EditorImportAnimationOptions() { + + + updating=false; + + flags = memnew( Tree ); + flags->set_hide_root(true); + TreeItem *root = flags->create_item(); + + const char ** fname=anim_flag_names; + const char ** fdescr=anim_flag_descript; + + while( *fname ) { + + TreeItem*ti = flags->create_item(root); + ti->set_cell_mode(0,TreeItem::CELL_MODE_CHECK); + ti->set_text(0,*fname); + ti->set_editable(0,true); + ti->set_tooltip(0,*fdescr); + items.push_back(ti); + fname++; + fdescr++; + } + + + add_margin_child("Animation Options",flags,true); + +} + + + + + + + +//////////////////////////////////////////////////////// + + + +void EditorSceneImportDialog::_choose_file(const String& p_path) { +#if 0 + StringName sn = EditorImportDB::get_singleton()->find_source_path(p_path); + if (sn!=StringName()) { + + EditorImportDB::ImportScene isc; + if (EditorImportDB::get_singleton()->get_scene(sn,isc)==OK) { + + save_path->set_text(String(sn).get_base_dir()); + texture_options->set_flags( isc.image_flags ); + texture_options->set_quality( isc.image_quality ); + texture_options->set_format( isc.image_format ); + animation_options->set_flags( isc.anim_flags ); + script_path->set_text( isc.import_script ); + uint32_t f = isc.flags; + for(int i=0;i<scene_flags.size();i++) { + + scene_flags[i]->set_checked(0,f&(1<<i)); + } + } + } else { +#endif + save_path->set_text(""); + //save_path->set_text(p_path.get_file().basename()+".scn"); +#if 0 + } +#endif + + import_path->set_text(p_path); + +} +void EditorSceneImportDialog::_choose_save_file(const String& p_path) { + + save_path->set_text(p_path); +} + +void EditorSceneImportDialog::_choose_script(const String& p_path) { + + String p = Globals::get_singleton()->localize_path(p_path); + if (!p.is_resource_file()) + p=Globals::get_singleton()->get_resource_path().path_to(p_path.get_base_dir())+p_path.get_file(); + script_path->set_text(p); + +} + +void EditorSceneImportDialog::_import() { + +//' ImportMonitorBlock imb; + + if (import_path->get_text()=="") { + error_dialog->set_text("Source path is empty."); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + if (save_path->get_text()=="") { + error_dialog->set_text("Target path is empty."); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + String dst_path; + + if (texture_action->get_selected()==0) + dst_path=save_path->get_text();//.get_base_dir(); + else + dst_path=Globals::get_singleton()->get("import/shared_textures"); + + uint32_t flags=0; + + for(int i=0;i<scene_flags.size();i++) { + + if (scene_flags[i]->is_checked(0)) + flags|=(1<<i); + } + + + Ref<EditorScenePostImport> pi; + + if (script_path->get_text()!="") { + Ref<Script> scr = ResourceLoader::load(script_path->get_text()); + if (!scr.is_valid()) { + error_dialog->set_text("Couldn't load Post-Import Script."); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + pi = Ref<EditorScenePostImport>( memnew( EditorScenePostImport ) ); + pi->set_script(scr.get_ref_ptr()); + if (!pi->get_script_instance()) { + + error_dialog->set_text("Invalid/Broken Script for Post-Import."); + error_dialog->popup_centered(Size2(200,100)); + return; + } + } + + + String save_file = save_path->get_text().plus_file(import_path->get_text().get_file().basename()+".scn"); + print_line("Saving to: "+save_file); + + + + Node *scene=NULL; + + + Ref<ResourceImportMetadata> rim = memnew( ResourceImportMetadata ); + + rim->add_source(EditorImportPlugin::validate_source_path(import_path->get_text())); + rim->set_option("flags",flags); + rim->set_option("texture_flags",texture_options->get_flags()); + rim->set_option("texture_format",texture_options->get_format()); + rim->set_option("texture_quality",texture_options->get_quality()); + rim->set_option("animation_flags",animation_options->get_flags()); + rim->set_option("post_import_script",script_path->get_text()!=String()?EditorImportPlugin::validate_source_path(script_path->get_text()):String()); + rim->set_option("reimport",true); + + Error err = plugin->import(save_file,rim); + + if (err) { + + error_dialog->set_text("Error importing scene."); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + hide(); + + /* + editor->clear_scene(); + + Error err = EditorImport::import_scene(import_path->get_text(),save_file,dst_path,flags,texture_options->get_format(),compression,texture_options->get_flags(),texture_options->get_quality(),animation_options->get_flags(), &scene,pi); + + if (err) { + + error_dialog->set_text("Error importing scene."); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + editor->save_import_export(); + if (scene) + editor->set_edited_scene(scene); + + hide(); + */ +}; + +void EditorSceneImportDialog::_browse() { + + file_select->popup_centered_ratio(); +} + +void EditorSceneImportDialog::_browse_target() { + + if (save_path->get_text()!="") + save_select->set_current_path(save_path->get_text()); + save_select->popup_centered_ratio(); + +} + +void EditorSceneImportDialog::_browse_script() { + + script_select->popup_centered_ratio(); + +} + +void EditorSceneImportDialog::popup_import(const String &p_from) { + + popup_centered(Size2(700,500)); + if (p_from!="") { + Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_from); + if (rimd.is_null()) + return; + + int flags = rimd->get_option("flags"); + + for(int i=0;i<scene_flags.size();i++) { + + scene_flags[i]->set_checked(0,flags&(1<<i)); + } + + texture_options->set_flags(rimd->get_option("texture_flags")); + texture_options->set_format(EditorTextureImportPlugin::ImageFormat(int(rimd->get_option("texture_format")))); + texture_options->set_quality(rimd->get_option("texture_quality")); + animation_options->set_flags(rimd->get_option("animation_flags")); + script_path->set_text(rimd->get_option("post_import_script")); + + save_path->set_text(p_from.get_base_dir()); + import_path->set_text(EditorImportPlugin::expand_source_path(rimd->get_source_path(0))); + + } +} + + +void EditorSceneImportDialog::_notification(int p_what) { + + + if (p_what==NOTIFICATION_ENTER_SCENE) { + + + List<String> extensions; + file_select->clear_filters(); + + for(int i=0;i<plugin->get_importers().size();i++) { + plugin->get_importers()[i]->get_extensions(&extensions); + } + + + for(int i=0;i<extensions.size();i++) { + + file_select->add_filter("*."+extensions[i]+" ; "+extensions[i].to_upper()); + } + + extensions.clear(); + + //EditorImport::get_import_extensions(&extensions) + /* ResourceLoader::get_recognized_extensions_for_type("PackedScene",&extensions); + save_select->clear_filters(); + for(int i=0;i<extensions.size();i++) { + + save_select->add_filter("*."+extensions[i]+" ; "+extensions[i].to_upper()); + }*/ + + + } +} + +Error EditorSceneImportDialog::import(const String& p_from, const String& p_to, const String& p_preset) { + + import_path->set_text(p_from); + save_path->set_text(p_to); + script_path->set_text(p_preset); + + _import(); + + + + return OK; +} + +void EditorSceneImportDialog::_bind_methods() { + + + ObjectTypeDB::bind_method("_choose_file",&EditorSceneImportDialog::_choose_file); + ObjectTypeDB::bind_method("_choose_save_file",&EditorSceneImportDialog::_choose_save_file); + ObjectTypeDB::bind_method("_choose_script",&EditorSceneImportDialog::_choose_script); + ObjectTypeDB::bind_method("_import",&EditorSceneImportDialog::_import); + ObjectTypeDB::bind_method("_browse",&EditorSceneImportDialog::_browse); + ObjectTypeDB::bind_method("_browse_target",&EditorSceneImportDialog::_browse_target); + ObjectTypeDB::bind_method("_browse_script",&EditorSceneImportDialog::_browse_script); + ADD_SIGNAL( MethodInfo("imported",PropertyInfo(Variant::OBJECT,"scene")) ); +} + + + +static const char *scene_flag_names[]={ + "Create Collisions (-col,-colonly)", + "Create Portals (-portal)", + "Create Rooms (-room)", + "Simplify Rooms", + "Create Billboards (-bb)", + "Create Impostors (-imp:dist)", + "Create LODs (-lod:dist)", + "Remove Nodes (-noimp)", + "Import Animations", + "Compress Geometry", + "Fail on Missing Images", + "Force Generation of Tangent Arrays", + NULL +}; + + +EditorSceneImportDialog::EditorSceneImportDialog(EditorNode *p_editor, EditorSceneImportPlugin *p_plugin) { + + + editor=p_editor; + plugin=p_plugin; + + set_title("Import 3D Scene"); + HBoxContainer *import_hb = memnew( HBoxContainer ); + add_child(import_hb); + set_child_rect(import_hb); + + VBoxContainer *vbc = memnew( VBoxContainer ); + import_hb->add_child(vbc); + vbc->set_h_size_flags(SIZE_EXPAND_FILL); + + HBoxContainer *hbc = memnew( HBoxContainer ); + vbc->add_margin_child("Source Scene:",hbc); + + import_path = memnew( LineEdit ); + import_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(import_path); + + Button * import_choose = memnew( Button ); + import_choose->set_text(" .. "); + hbc->add_child(import_choose); + + import_choose->connect("pressed", this,"_browse"); + + hbc = memnew( HBoxContainer ); + vbc->add_margin_child("Target Scene:",hbc); + + save_path = memnew( LineEdit ); + save_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(save_path); + + Button * save_choose = memnew( Button ); + save_choose->set_text(" .. "); + hbc->add_child(save_choose); + + save_choose->connect("pressed", this,"_browse_target"); + + texture_action = memnew( OptionButton ); + texture_action->add_item("Same as Target Scene"); + texture_action->add_item("Shared"); + texture_action->select(0); + vbc->add_margin_child("Target Texture Folder:",texture_action); + + import_options = memnew( Tree ); + vbc->set_v_size_flags(SIZE_EXPAND_FILL); + vbc->add_margin_child("Options:",import_options,true); + + file_select = memnew(FileDialog); + file_select->set_access(FileDialog::ACCESS_FILESYSTEM); + add_child(file_select); + + + file_select->set_mode(FileDialog::MODE_OPEN_FILE); + + file_select->connect("file_selected", this,"_choose_file"); + + save_select = memnew(EditorDirDialog); + add_child(save_select); + + //save_select->set_mode(FileDialog::MODE_SAVE_FILE); + save_select->connect("dir_selected", this,"_choose_save_file"); + + get_ok()->connect("pressed", this,"_import"); + get_ok()->set_text("Import"); + + TreeItem *root = import_options->create_item(NULL); + import_options->set_hide_root(true); + + + + + TreeItem *importopts = import_options->create_item(root); + importopts->set_text(0,"Import:"); + + const char ** fn=scene_flag_names; + + while(*fn) { + + TreeItem *opt = import_options->create_item(importopts); + opt->set_cell_mode(0,TreeItem::CELL_MODE_CHECK); + opt->set_checked(0,true); + opt->set_editable(0,true); + opt->set_text(0,*fn); + scene_flags.push_back(opt); + fn++; + } + + hbc = memnew( HBoxContainer ); + vbc->add_margin_child("Post-Process Script:",hbc); + + script_path = memnew( LineEdit ); + script_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(script_path); + + Button * script_choose = memnew( Button ); + script_choose->set_text(" .. "); + hbc->add_child(script_choose); + + script_choose->connect("pressed", this,"_browse_script"); + + script_select = memnew(FileDialog); + add_child(script_select); + for(int i=0;i<ScriptServer::get_language_count();i++) { + + ScriptLanguage *sl=ScriptServer::get_language(i); + String ext = sl->get_extension(); + if (ext=="") + continue; + script_select->add_filter("*."+ext+" ; "+sl->get_name()); + } + + + script_select->set_mode(FileDialog::MODE_OPEN_FILE); + + script_select->connect("file_selected", this,"_choose_script"); + + error_dialog = memnew ( ConfirmationDialog ); + add_child(error_dialog); + error_dialog->get_ok()->set_text("Accept"); +// error_dialog->get_cancel()->hide(); + + set_hide_on_ok(false); + + GLOBAL_DEF("import/shared_textures","res://"); + Globals::get_singleton()->set_custom_property_info("import/shared_textures",PropertyInfo(Variant::STRING,"import/shared_textures",PROPERTY_HINT_DIR)); + + import_hb->add_constant_override("separation",30); + + VBoxContainer *ovb = memnew( VBoxContainer); + ovb->set_h_size_flags(SIZE_EXPAND_FILL); + import_hb->add_child(ovb); + + texture_options = memnew( EditorImportTextureOptions ); + ovb->add_child(texture_options); + texture_options->set_v_size_flags(SIZE_EXPAND_FILL); + //animation_options->set_flags(EditorImport:: + texture_options->set_format(EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM); + + animation_options = memnew( EditorImportAnimationOptions ); + ovb->add_child(animation_options); + animation_options->set_v_size_flags(SIZE_EXPAND_FILL); + animation_options->set_flags(EditorSceneAnimationImportPlugin::ANIMATION_DETECT_LOOP|EditorSceneAnimationImportPlugin::ANIMATION_KEEP_VALUE_TRACKS|EditorSceneAnimationImportPlugin::ANIMATION_OPTIMIZE); + + //texture_options->set_format(EditorImport::IMAGE_FORMAT_C); + +} + + + +//////////////////////////////// + + + +String EditorSceneImportPlugin::get_name() const { + + return "scene_3d"; +} + +String EditorSceneImportPlugin::get_visible_name() const{ + + return "3D Scene"; +} + +void EditorSceneImportPlugin::import_dialog(const String& p_from){ + + dialog->popup_import(p_from); +} + + +////////////////////////// + + +static bool _teststr(const String& p_what,const String& p_str) { + + if (p_what.findn("$"+p_str)!=-1) //blender and other stuff + return true; + if (p_what.to_lower().ends_with("-"+p_str)) //collada only supports "_" and "-" besides letters + return true; + return false; +} + +static String _fixstr(const String& p_what,const String& p_str) { + + if (p_what.findn("$"+p_str)!=-1) //blender and other stuff + return p_what.replace("$"+p_str,""); + if (p_what.to_lower().ends_with("-"+p_str)) //collada only supports "_" and "-" besides letters + return p_what.substr(0,p_what.length()-(p_str.length()+1)); + return p_what; +} + + + +void EditorSceneImportPlugin::_find_resources(const Variant& p_var,Set<Ref<ImageTexture> >& image_map) { + + + switch(p_var.get_type()) { + + case Variant::OBJECT: { + + Ref<Resource> res = p_var; + if (res.is_valid()) { + + if (res->is_type("Texture")) { + + image_map.insert(res); + + + } else { + + + List<PropertyInfo> pl; + res->get_property_list(&pl); + for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) { + + if (E->get().type==Variant::OBJECT || E->get().type==Variant::ARRAY || E->get().type==Variant::DICTIONARY) { + _find_resources(res->get(E->get().name),image_map); + } + } + + } + } + + } break; + case Variant::DICTIONARY: { + + Dictionary d= p_var; + + List<Variant> keys; + d.get_key_list(&keys); + + for(List<Variant>::Element *E=keys.front();E;E=E->next()) { + + + _find_resources(E->get(),image_map); + _find_resources(d[E->get()],image_map); + + } + + + } break; + case Variant::ARRAY: { + + Array a = p_var; + for(int i=0;i<a.size();i++) { + + _find_resources(a[i],image_map); + } + + } break; + + } + +} + + +Node* EditorSceneImportPlugin::_fix_node(Node *p_node,Node *p_root,Map<Ref<Mesh>,Ref<Shape> > &collision_map,uint32_t p_flags,Set<Ref<ImageTexture> >& image_map) { + + // children first.. + for(int i=0;i<p_node->get_child_count();i++) { + + + Node *r = _fix_node(p_node->get_child(i),p_root,collision_map,p_flags,image_map); + if (!r) { + print_line("was erased.."); + i--; //was erased + } + } + + String name = p_node->get_name(); + + bool isroot = p_node==p_root; + + + if (!isroot && p_flags&SCENE_FLAG_REMOVE_NOIMP && _teststr(name,"noimp")) { + + memdelete(p_node); + return NULL; + } + + { + + List<PropertyInfo> pl; + p_node->get_property_list(&pl); + for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) { + + if (E->get().type==Variant::OBJECT || E->get().type==Variant::ARRAY || E->get().type==Variant::DICTIONARY) { + _find_resources(p_node->get(E->get().name),image_map); + } + } + + } + + + + + if (p_flags&SCENE_FLAG_CREATE_BILLBOARDS && p_node->cast_to<MeshInstance>()) { + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + + bool bb=false; + + if ((_teststr(name,"bb"))) { + bb=true; + } else if (mi->get_mesh().is_valid() && (_teststr(mi->get_mesh()->get_name(),"bb"))) { + bb=true; + + } + + if (bb) { + mi->set_flag(GeometryInstance::FLAG_BILLBOARD,true); + if (mi->get_mesh().is_valid()) { + + Ref<Mesh> m = mi->get_mesh(); + for(int i=0;i<m->get_surface_count();i++) { + + Ref<FixedMaterial> fm = m->surface_get_material(i); + if (fm.is_valid()) { + fm->set_flag(Material::FLAG_UNSHADED,true); + fm->set_flag(Material::FLAG_DOUBLE_SIDED,true); + fm->set_hint(Material::HINT_NO_DEPTH_DRAW,true); + fm->set_fixed_flag(FixedMaterial::FLAG_USE_ALPHA,true); + } + } + } + } + } + + if (p_flags&SCENE_FLAG_REMOVE_NOIMP && p_node->cast_to<AnimationPlayer>()) { + //remove animations referencing non-importable nodes + AnimationPlayer *ap = p_node->cast_to<AnimationPlayer>(); + + List<StringName> anims; + ap->get_animation_list(&anims); + for(List<StringName>::Element *E=anims.front();E;E=E->next()) { + + Ref<Animation> anim=ap->get_animation(E->get()); + ERR_CONTINUE(anim.is_null()); + for(int i=0;i<anim->get_track_count();i++) { + NodePath path = anim->track_get_path(i); + + for(int j=0;j<path.get_name_count();j++) { + String node = path.get_name(j); + if (_teststr(node,"noimp")) { + anim->remove_track(i); + i--; + break; + } + } + } + + } + } + + + if (p_flags&SCENE_FLAG_CREATE_IMPOSTORS && p_node->cast_to<MeshInstance>()) { + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + + String str; + + if ((_teststr(name,"imp"))) { + str=name; + } else if (mi->get_mesh().is_valid() && (_teststr(mi->get_mesh()->get_name(),"imp"))) { + str=mi->get_mesh()->get_name(); + + } + + + if (p_node->get_parent() && p_node->get_parent()->cast_to<MeshInstance>()) { + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + MeshInstance *mip = p_node->get_parent()->cast_to<MeshInstance>(); + String d=str.substr(str.find("imp")+3,str.length()); + if (d!="") { + if ((d[0]<'0' || d[0]>'9')) + d=d.substr(1,d.length()); + if (d.length() && d[0]>='0' && d[0]<='9') { + float dist = d.to_double(); + mi->set_flag(GeometryInstance::FLAG_BILLBOARD,true); + mi->set_flag(GeometryInstance::FLAG_BILLBOARD_FIX_Y,true); + mi->set_draw_range_begin(dist); + mi->set_draw_range_end(100000); + + mip->set_draw_range_begin(0); + mip->set_draw_range_end(dist); + + if (mi->get_mesh().is_valid()) { + + Ref<Mesh> m = mi->get_mesh(); + for(int i=0;i<m->get_surface_count();i++) { + + Ref<FixedMaterial> fm = m->surface_get_material(i); + if (fm.is_valid()) { + fm->set_flag(Material::FLAG_UNSHADED,true); + fm->set_flag(Material::FLAG_DOUBLE_SIDED,true); + fm->set_hint(Material::HINT_NO_DEPTH_DRAW,true); + fm->set_fixed_flag(FixedMaterial::FLAG_USE_ALPHA,true); + } + } + } + } + } + } + } + + if (p_flags&SCENE_FLAG_CREATE_LODS && p_node->cast_to<MeshInstance>()) { + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + + String str; + + if ((_teststr(name,"lod"))) { + str=name; + } else if (mi->get_mesh().is_valid() && (_teststr(mi->get_mesh()->get_name(),"lod"))) { + str=mi->get_mesh()->get_name(); + + } + + + if (p_node->get_parent() && p_node->get_parent()->cast_to<MeshInstance>()) { + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + MeshInstance *mip = p_node->get_parent()->cast_to<MeshInstance>(); + String d=str.substr(str.find("lod")+3,str.length()); + if (d!="") { + if ((d[0]<'0' || d[0]>'9')) + d=d.substr(1,d.length()); + if (d.length() && d[0]>='0' && d[0]<='9') { + float dist = d.to_double(); + mi->set_draw_range_begin(dist); + mi->set_draw_range_end(100000); + + mip->set_draw_range_begin(0); + mip->set_draw_range_end(dist); + + /*if (mi->get_mesh().is_valid()) { + + Ref<Mesh> m = mi->get_mesh(); + for(int i=0;i<m->get_surface_count();i++) { + + Ref<FixedMaterial> fm = m->surface_get_material(i); + if (fm.is_valid()) { + fm->set_flag(Material::FLAG_UNSHADED,true); + fm->set_flag(Material::FLAG_DOUBLE_SIDED,true); + fm->set_hint(Material::HINT_NO_DEPTH_DRAW,true); + fm->set_fixed_flag(FixedMaterial::FLAG_USE_ALPHA,true); + } + } + }*/ + } + } + } + } + if (p_flags&SCENE_FLAG_CREATE_COLLISIONS && _teststr(name,"colonly") && p_node->cast_to<MeshInstance>()) { + + if (isroot) + return p_node; + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + Node * col = mi->create_trimesh_collision_node(); + + ERR_FAIL_COND_V(!col,NULL); + col->set_name(_fixstr(name,"colonly")); + col->cast_to<Spatial>()->set_transform(mi->get_transform()); + p_node->replace_by(col); + memdelete(p_node); + p_node=col; + + } else if (p_flags&SCENE_FLAG_CREATE_COLLISIONS &&_teststr(name,"col") && p_node->cast_to<MeshInstance>()) { + + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + + mi->set_name(_fixstr(name,"col")); + mi->create_trimesh_collision(); + } else if (p_flags&SCENE_FLAG_CREATE_ROOMS && _teststr(name,"room") && p_node->cast_to<MeshInstance>()) { + + + if (isroot) + return p_node; + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + DVector<Face3> faces = mi->get_faces(VisualInstance::FACES_SOLID); + + + BSP_Tree bsptree(faces); + + Ref<RoomBounds> area = memnew( RoomBounds ); + area->set_bounds(faces); + area->set_geometry_hint(faces); + + + Room * room = memnew( Room ); + room->set_name(_fixstr(name,"room")); + room->set_transform(mi->get_transform()); + room->set_room(area); + + p_node->replace_by(room); + memdelete(p_node); + p_node=room; + + } else if (p_flags&SCENE_FLAG_CREATE_ROOMS &&_teststr(name,"room")) { + + if (isroot) + return p_node; + + Spatial *dummy = p_node->cast_to<Spatial>(); + ERR_FAIL_COND_V(!dummy,NULL); + + Room * room = memnew( Room ); + room->set_name(_fixstr(name,"room")); + room->set_transform(dummy->get_transform()); + + p_node->replace_by(room); + memdelete(p_node); + p_node=room; + + room->compute_room_from_subtree(); + + } else if (p_flags&SCENE_FLAG_CREATE_PORTALS &&_teststr(name,"portal") && p_node->cast_to<MeshInstance>()) { + + if (isroot) + return p_node; + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + DVector<Face3> faces = mi->get_faces(VisualInstance::FACES_SOLID); + + ERR_FAIL_COND_V(faces.size()==0,NULL); + //step 1 compute the plane + Set<Vector3> points; + Plane plane; + + Vector3 center; + + for(int i=0;i<faces.size();i++) { + + Face3 f = faces.get(i); + Plane p = f.get_plane(); + plane.normal+=p.normal; + plane.d+=p.d; + + for(int i=0;i<3;i++) { + + Vector3 v = f.vertex[i].snapped(0.01); + if (!points.has(v)) { + points.insert(v); + center+=v; + } + } + } + + plane.normal.normalize(); + plane.d/=faces.size(); + center/=points.size(); + + //step 2, create points + + Transform t; + t.basis.from_z(plane.normal); + t.basis.transpose(); + t.origin=center; + + Vector<Point2> portal_points; + + for(Set<Vector3>::Element *E=points.front();E;E=E->next()) { + + Vector3 local = t.xform_inv(E->get()); + portal_points.push_back(Point2(local.x,local.y)); + } + // step 3 bubbly sort points + + int swaps=0; + + do { + swaps=0; + + for(int i=0;i<portal_points.size()-1;i++) { + + float a = portal_points[i].atan2(); + float b = portal_points[i+1].atan2(); + + if (a>b) { + SWAP( portal_points[i], portal_points[i+1] ); + swaps++; + } + + } + + } while(swaps); + + + Portal *portal = memnew( Portal ); + + portal->set_shape(portal_points); + portal->set_transform( mi->get_transform() * t); + + p_node->replace_by(portal); + memdelete(p_node); + p_node=portal; + + } else if (p_node->cast_to<MeshInstance>()) { + + //last attempt, maybe collision insde the mesh data + + MeshInstance *mi = p_node->cast_to<MeshInstance>(); + + Ref<Mesh> mesh = mi->get_mesh(); + if (!mesh.is_null()) { + + if (p_flags&SCENE_FLAG_CREATE_COLLISIONS && _teststr(mesh->get_name(),"col")) { + + mesh->set_name( _fixstr(mesh->get_name(),"col") ); + Ref<Shape> shape; + + if (collision_map.has(mesh)) { + shape = collision_map[mesh]; + + } else { + + shape = mesh->create_trimesh_shape(); + if (!shape.is_null()) + collision_map[mesh]=shape; + + + } + + if (!shape.is_null()) { +#if 0 + StaticBody* static_body = memnew( StaticBody ); + ERR_FAIL_COND_V(!static_body,NULL); + static_body->set_name( String(mesh->get_name()) + "_col" ); + shape->set_name(static_body->get_name()); + static_body->add_shape(shape); + + mi->add_child(static_body); + if (mi->get_owner()) + static_body->set_owner( mi->get_owner() ); +#endif + } + + } + + } + + } + + + + + return p_node; +} + + +void EditorSceneImportPlugin::_merge_node(Node *p_node,Node*p_root,Node *p_existing,Set<Ref<Resource> >& checked_resources) { + + + NodePath path = p_root->get_path_to(p_node); + + bool valid=false; + + if (p_existing->has_node(path)) { + + Node *existing = p_existing->get_node(path); + + if (existing->get_type()==p_node->get_type()) { + //same thing, check what it is + + if (existing->get_type()=="MeshInstance") { + + //merge mesh instance, this is a special case! + MeshInstance *mi_existing=existing->cast_to<MeshInstance>(); + MeshInstance *mi_node=p_node->cast_to<MeshInstance>(); + + Ref<Mesh> mesh_existing = mi_existing->get_mesh(); + Ref<Mesh> mesh_node = mi_node->get_mesh(); + + if (mesh_existing.is_null() || checked_resources.has(mesh_node)) { + + if (mesh_node.is_valid()) + mi_existing->set_mesh(mesh_node); + } else if (mesh_node.is_valid()) { + + //mesh will always be overwritten, so check materials from original + + for(int i=0;i<mesh_node->get_surface_count();i++) { + + String name = mesh_node->surface_get_name(i); + + if (name!="") { + + for(int j=0;j<mesh_existing->get_surface_count();j++) { + + Ref<Material> keep; + + if (name==mesh_existing->surface_get_name(j)) { + + Ref<Material> mat = mesh_existing->surface_get_material(j); + + if (mat.is_valid()) { + if (mat->get_path()!="" && mat->get_path().begins_with("res://") && mat->get_path().find("::")==-1) { + keep=mat; //mat was loaded from file + } else if (mat->is_edited()) { + keep=mat; //mat was edited + } + } + break; + } + if (keep.is_valid()) + mesh_node->surface_set_material(i,keep); //kept + } + } + } + + mi_existing->set_mesh(mesh_node); //always overwrite mesh + checked_resources.insert(mesh_node); + + } + } else if (existing->get_type()=="Path") { + + Path *path_existing =existing->cast_to<Path>(); + Path *path_node =p_node->cast_to<Path>(); + + if (path_node->get_curve().is_valid()) { + + if (!path_existing->get_curve().is_valid() || !path_existing->get_curve()->is_edited()) { + path_existing->set_curve(path_node->get_curve()); + } + } + } + } + + valid=true; + } else { + + if (p_node!=p_root && p_existing->has_node(p_root->get_path_to(p_node->get_parent()))) { + + Node *parent = p_existing->get_node(p_root->get_path_to(p_node->get_parent())); + NodePath path = p_root->get_path_to(p_node); + + //add it.. because not existing in existing scene + Object *o = ObjectTypeDB::instance(p_existing->get_type()); + Node *n=NULL; + if (o) + n=o->cast_to<Node>(); + + if (n) { + + List<PropertyInfo> pl; + p_existing->get_property_list(&pl); + for(List<PropertyInfo>::Element *E=pl.front();E;E=E->next()) { + if (!(E->get().usage&PROPERTY_USAGE_STORAGE)) + continue; + n->set( E->get().name, p_existing->get(E->get().name) ); + } + + parent->add_child(n); + + valid=true; + } + } + + } + + + if (valid) { + + for(int i=0;i<p_node->get_child_count();i++) { + _merge_node(p_node->get_child(i),p_root,p_existing,checked_resources); + } + } + +} + + +void EditorSceneImportPlugin::_merge_scenes(Node *p_existing,Node *p_new) { + + Set<Ref<Resource> > checked_resources; + _merge_node(p_new,p_new,p_existing,checked_resources); + +} + +#if 0 + +Error EditorImport::import_scene(const String& p_path,const String& p_dest_path,const String& p_resource_path,uint32_t p_flags,ImageFormat p_image_format,ImageCompression p_image_compression,uint32_t p_image_flags,float p_quality,uint32_t animation_flags,Node **r_scene,Ref<EditorPostImport> p_post_import) { + + +} +#endif + +Error EditorSceneImportPlugin::import(const String& p_dest_path, const Ref<ResourceImportMetadata>& p_from){ + + Ref<ResourceImportMetadata> from=p_from; + + ERR_FAIL_COND_V(from->get_source_count()!=1,ERR_INVALID_PARAMETER); + + String src_path=EditorImportPlugin::expand_source_path(from->get_source_path(0)); + + Ref<EditorSceneImporter> importer; + String ext=src_path.extension().to_lower(); + + + EditorNode::progress_add_task("import","Import Scene",104); + for(int i=0;i<importers.size();i++) { + + List<String> extensions; + importers[i]->get_extensions(&extensions); + + for(List<String>::Element *E=extensions.front();E;E=E->next()) { + + if (E->get().to_lower()==ext) { + + importer = importers[i]; + break; + } + } + + if (importer.is_valid()) + break; + } + + if (!importer.is_valid()) { + EditorNode::progress_end_task("import"); + } + ERR_FAIL_COND_V(!importer.is_valid(),ERR_FILE_UNRECOGNIZED); + + int animation_flags=p_from->get_option("animation_flags"); + int scene_flags = from->get_option("flags"); + + uint32_t import_flags=0; + if (animation_flags&EditorSceneAnimationImportPlugin::ANIMATION_DETECT_LOOP) + import_flags|=EditorSceneImporter::IMPORT_ANIMATION_DETECT_LOOP; + if (animation_flags&EditorSceneAnimationImportPlugin::ANIMATION_OPTIMIZE) + import_flags|=EditorSceneImporter::IMPORT_ANIMATION_OPTIMIZE; + if (scene_flags&SCENE_FLAG_IMPORT_ANIMATIONS) + import_flags|=EditorSceneImporter::IMPORT_ANIMATION; + if (scene_flags&SCENE_FLAG_FAIL_ON_MISSING_IMAGES) + import_flags|=EditorSceneImporter::IMPORT_FAIL_ON_MISSING_DEPENDENCIES; + if (scene_flags&SCENE_FLAG_GENERATE_TANGENT_ARRAYS) + import_flags|=EditorSceneImporter::IMPORT_GENERATE_TANGENT_ARRAYS; + + + + EditorNode::progress_task_step("import","Importing Scene..",0); + + + Error err=OK; + Node *scene = importer->import_scene(src_path,import_flags,&err); + if (!scene || err!=OK) { + EditorNode::progress_end_task("import"); + return err; + } + + + bool merge = !bool(from->get_option("reimport")); + from->set_source_md5(0,FileAccess::get_md5(src_path)); + from->set_editor(get_name()); + + from->set_option("reimport",false); + String target_res_path=p_dest_path.get_base_dir(); + + Map<Ref<Mesh>,Ref<Shape> > collision_map; + + Ref<ResourceImportMetadata> imd = memnew(ResourceImportMetadata); + + Set< Ref<ImageTexture> > imagemap; + EditorNode::progress_task_step("import","Post-Processing Scene..",1); + + + + scene=_fix_node(scene,scene,collision_map,scene_flags,imagemap); + + + /// BEFORE ANYTHING, RUN SCRIPT + + EditorNode::progress_task_step("import","Running Custom Script..",2); + + String post_import_script_path = from->get_option("post_import_script"); + Ref<EditorScenePostImport> post_import_script; + + if (post_import_script_path!="") { + post_import_script_path = EditorImportPlugin::expand_source_path(post_import_script_path); + Ref<Script> scr = ResourceLoader::load(post_import_script_path); + if (!scr.is_valid()) { + EditorNode::add_io_error("Couldn't load post-import script: '"+post_import_script_path); + } else { + + post_import_script = Ref<EditorScenePostImport>( memnew( EditorScenePostImport ) ); + post_import_script->set_script(scr.get_ref_ptr()); + if (!post_import_script->get_script_instance()) { + EditorNode::add_io_error("Invalid/Broken Script for Post-Import: '"+post_import_script_path); + post_import_script.unref(); + } + } + } + + + if (post_import_script.is_valid()) { + err = post_import_script->post_import(scene); + if (err) { + EditorNode::add_io_error("Error running Post-Import script: '"+post_import_script_path); + EditorNode::progress_end_task("import"); + return err; + } + } + + /// IMPORT IMAGES + + + int idx=0; + + int image_format = from->get_option("texture_format"); + int image_flags = from->get_option("texture_flags"); + float image_quality = from->get_option("texture_quality"); + + for (Set< Ref<ImageTexture> >::Element *E=imagemap.front();E;E=E->next()) { + + //texture could be converted to something more useful for 3D, that could load individual mipmaps and stuff + //but not yet.. + + Ref<ImageTexture> texture = E->get(); + + ERR_CONTINUE(!texture.is_valid()); + + String path = texture->get_path(); + String fname= path.get_file(); + String target_path = Globals::get_singleton()->localize_path(target_res_path.plus_file(fname)); + EditorNode::progress_task_step("import","Import Img: "+fname,3+(idx)*100/imagemap.size()); + + idx++; + + if (path==target_path) { + + EditorNode::add_io_error("Can't import a file over itself: '"+target_path); + continue; + } + + if (!target_path.begins_with("res://")) { + EditorNode::add_io_error("Couldn't localize path: '"+target_path+"' (already local)"); + continue; + } + + + { + + + target_path=target_path.basename()+".tex"; + + if (FileAccess::exists(target_path)) { + texture->set_path(target_path); + continue; //already imported + } + Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata ); + imd->set_option("flags",image_flags); + imd->set_option("format",image_format); + imd->set_option("quality",image_quality); + imd->set_option("atlas",false); + imd->add_source(EditorImportPlugin::validate_source_path(path)); + + Error err = EditorTextureImportPlugin::get_singleton(EditorTextureImportPlugin::MODE_TEXTURE_3D)->import(target_path,imd); + + } + } + + + /// BEFORE SAVING - MERGE + + + if (merge) { + + EditorNode::progress_task_step("import","Merging..",103); + + FileAccess *fa = FileAccess::create(FileAccess::ACCESS_FILESYSTEM); + if (fa->file_exists(p_dest_path)) { + + //try to merge + + Ref<PackedScene> s = ResourceLoader::load(p_dest_path); + if (s.is_valid()) { + + Node *existing = s->instance(true); + + if (existing) { + + _merge_scenes(scene,existing); + + memdelete(scene); + scene=existing; + } + } + + } + + memdelete(fa); + } + + + EditorNode::progress_task_step("import","Saving..",104); + + Ref<PackedScene> packer = memnew( PackedScene ); + packer->pack(scene); + packer->set_path(p_dest_path); + packer->set_import_metadata(from); + + print_line("SAVING TO: "+p_dest_path); + err = ResourceSaver::save(p_dest_path,packer); + + //EditorFileSystem::get_singleton()->update_resource(packer); + + memdelete(scene); + /* + scene->set_filename(p_dest_path); + if (r_scene) { + *r_scene=scene; + } else { + memdelete(scene); + } + + String sp; + if (p_post_import.is_valid() && !p_post_import->get_script().is_null()) { + Ref<Script> scr = p_post_import->get_script(); + if (scr.is_valid()) + sp=scr->get_path(); + } + + String op=_getrelpath(p_path,p_dest_path); + + */ + EditorNode::progress_end_task("import"); + + return err; + +} + +void EditorSceneImportPlugin::add_importer(const Ref<EditorSceneImporter>& p_importer) { + + importers.push_back(p_importer); +} + + +EditorSceneImportPlugin::EditorSceneImportPlugin(EditorNode* p_editor) { + + dialog = memnew( EditorSceneImportDialog(p_editor,this) ); + p_editor->get_gui_base()->add_child(dialog); +} + + +/////////////////////////////// + + +String EditorSceneAnimationImportPlugin::get_name() const { + + return "anim_3d"; +} +String EditorSceneAnimationImportPlugin::get_visible_name() const{ + + + return "3D Scene Animation"; +} +void EditorSceneAnimationImportPlugin::import_dialog(const String& p_from){ + + +} +Error EditorSceneAnimationImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from){ + + return OK; +} + +EditorSceneAnimationImportPlugin::EditorSceneAnimationImportPlugin(EditorNode* p_editor) { + + +} + diff --git a/tools/editor/io_plugins/editor_scene_import_plugin.h b/tools/editor/io_plugins/editor_scene_import_plugin.h new file mode 100644 index 000000000..d4c96b97d --- /dev/null +++ b/tools/editor/io_plugins/editor_scene_import_plugin.h @@ -0,0 +1,167 @@ +/*************************************************************************/ +/* editor_scene_import_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_PLUGIN_H +#define EDITOR_SCENE_IMPORT_PLUGIN_H + +#include "scene/gui/dialogs.h" +#include "scene/gui/tree.h" +#include "scene/gui/label.h" +#include "scene/gui/option_button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/progress_bar.h" +#include "scene/gui/slider.h" +#include "scene/gui/spin_box.h" +#include "scene/resources/mesh.h" +#include "tools/editor/editor_file_system.h" +#include "tools/editor/editor_dir_dialog.h" +#include "tools/editor/editor_import_export.h" +#include "tools/editor/io_plugins/editor_texture_import_plugin.h" +#include "scene/resources/animation.h" + +class EditorNode; +class EditorSceneImportDialog; + +class EditorSceneImporter : public Reference { + + OBJ_TYPE(EditorSceneImporter,Reference ); +public: + + enum ImportFlags { + IMPORT_SCENE=1, + IMPORT_ANIMATION=2, + IMPORT_ANIMATION_DETECT_LOOP=4, + IMPORT_ANIMATION_OPTIMIZE=8, + IMPORT_GENERATE_TANGENT_ARRAYS=16, + IMPORT_FAIL_ON_MISSING_DEPENDENCIES=128 + + }; + + virtual uint32_t get_import_flags() const=0; + virtual void get_extensions(List<String> *r_extensions) const=0; + virtual Node* import_scene(const String& p_path,uint32_t p_flags,Error* r_err=NULL)=0; + virtual Ref<Animation> import_animation(const String& p_path,uint32_t p_flags)=0; + + + + EditorSceneImporter(); +}; + +///////////////////////////////////////// + + +//Plugin for post processing scenes or images + +class EditorScenePostImport : public Reference { + + OBJ_TYPE(EditorScenePostImport,Reference ); +protected: + + static void _bind_methods(); +public: + + virtual Error post_import(Node* p_scene); + EditorScenePostImport(); +}; + + +class EditorSceneImportPlugin : public EditorImportPlugin { + + OBJ_TYPE(EditorSceneImportPlugin,EditorImportPlugin); + + EditorSceneImportDialog *dialog; + + Vector<Ref<EditorSceneImporter> > importers; + + void _find_resources(const Variant& p_var,Set<Ref<ImageTexture> >& image_map); + Node* _fix_node(Node *p_node,Node *p_root,Map<Ref<Mesh>,Ref<Shape> > &collision_map,uint32_t p_flags,Set<Ref<ImageTexture> >& image_map); + void _merge_node(Node *p_node,Node*p_root,Node *p_existing,Set<Ref<Resource> >& checked_resources); + void _merge_scenes(Node *p_existing,Node *p_new); + + +public: + + enum SceneFlags { + + SCENE_FLAG_CREATE_COLLISIONS=1, + SCENE_FLAG_CREATE_PORTALS=2, + SCENE_FLAG_CREATE_ROOMS=4, + SCENE_FLAG_SIMPLIFY_ROOMS=8, + SCENE_FLAG_CREATE_BILLBOARDS=16, + SCENE_FLAG_CREATE_IMPOSTORS=32, + SCENE_FLAG_CREATE_LODS=64, + SCENE_FLAG_REMOVE_NOIMP=128, + SCENE_FLAG_IMPORT_ANIMATIONS=256, + SCENE_FLAG_COMPRESS_GEOMETRY=512, + SCENE_FLAG_FAIL_ON_MISSING_IMAGES=1024, + SCENE_FLAG_GENERATE_TANGENT_ARRAYS=2048, + SCENE_FLAG_DONT_SAVE_TO_DB=8192 + }; + + + virtual String get_name() const; + virtual String get_visible_name() const; + virtual void import_dialog(const String& p_from=""); + virtual Error import(const String& p_path, const Ref<ResourceImportMetadata>& p_from); + + void add_importer(const Ref<EditorSceneImporter>& p_importer); + const Vector<Ref<EditorSceneImporter> >& get_importers() { return importers; } + + EditorSceneImportPlugin(EditorNode* p_editor=NULL); + + +}; + + +class EditorSceneAnimationImportPlugin : public EditorImportPlugin { + + OBJ_TYPE(EditorSceneAnimationImportPlugin,EditorImportPlugin); +public: + + + enum AnimationFlags { + + ANIMATION_DETECT_LOOP=1, + ANIMATION_KEEP_VALUE_TRACKS=2, + ANIMATION_OPTIMIZE=4 + }; + + virtual String get_name() const; + virtual String get_visible_name() const; + virtual void import_dialog(const String& p_from=""); + virtual Error import(const String& p_path, const Ref<ResourceImportMetadata>& p_from); + + EditorSceneAnimationImportPlugin(EditorNode* p_editor=NULL); + + +}; + + + +#endif // EDITOR_SCENE_IMPORT_PLUGIN_H diff --git a/tools/editor/io_plugins/editor_texture_import_plugin.cpp b/tools/editor/io_plugins/editor_texture_import_plugin.cpp new file mode 100644 index 000000000..7c506a33b --- /dev/null +++ b/tools/editor/io_plugins/editor_texture_import_plugin.cpp @@ -0,0 +1,1177 @@ +/*************************************************************************/ +/* editor_texture_import_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "editor_texture_import_plugin.h" +#include "io/image_loader.h" +#include "tools/editor/editor_node.h" +#include "io/resource_saver.h" +#include "editor_atlas.h" +#include "tools/editor/editor_settings.h" +#include "io/md5.h" +#include "io/marshalls.h" +#include "globals.h" + +static const char *flag_names[]={ + "Streaming Format", + "Fix Border Alpha", + "Alpha Bit Hint", + "Compress Extra (PVRTC2)", + "No MipMaps", + "Repeat", + "Filter (Magnifying)", + NULL +}; + +static const char *flag_short_names[]={ + "Stream", + "FixBorder", + "AlphBit", + "ExtComp", + "NoMipMap", + "Repeat", + "Filter", + NULL +}; + + +void EditorImportTextureOptions::set_format(EditorTextureImportPlugin::ImageFormat p_format) { + + updating=true; + format->select(p_format); + if (p_format==EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY) { + quality_vb->show(); + } else { + quality_vb->hide(); + } + + updating=false; + +} + +EditorTextureImportPlugin::ImageFormat EditorImportTextureOptions::get_format() const{ + + return (EditorTextureImportPlugin::ImageFormat)format->get_selected(); + +} + +void EditorImportTextureOptions::set_flags(uint32_t p_flags){ + + updating=true; + for(int i=0;i<items.size();i++) { + + items[i]->set_checked(0,p_flags&(1<<i)); + } + updating=false; + +} + +void EditorImportTextureOptions::set_quality(float p_quality) { + + quality->set_val(p_quality); +} + +float EditorImportTextureOptions::get_quality() const { + + return quality->get_val(); +} + + +uint32_t EditorImportTextureOptions::get_flags() const{ + + uint32_t f=0; + for(int i=0;i<items.size();i++) { + + if (items[i]->is_checked(0)) + f|=(1<<i); + } + + return f; +} + +void EditorImportTextureOptions::_changedp(int p_value) { + + _changed(); +} + +void EditorImportTextureOptions::_changed() { + + if (updating) + return; + if (format->get_selected()==EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY) { + quality_vb->show(); + } else { + quality_vb->hide(); + } + + emit_signal("changed"); +} + + +void EditorImportTextureOptions::_bind_methods() { + + ObjectTypeDB::bind_method("_changed",&EditorImportTextureOptions::_changed); + ObjectTypeDB::bind_method("_changedp",&EditorImportTextureOptions::_changedp); + + ADD_SIGNAL(MethodInfo("changed")); +} + + +void EditorImportTextureOptions::_notification(int p_what) { + + if (p_what==NOTIFICATION_ENTER_SCENE) { + + flags->connect("item_edited",this,"_changed"); + format->connect("item_selected",this,"_changedp"); + } +} + +EditorImportTextureOptions::EditorImportTextureOptions() { + + + updating=false; + format = memnew( OptionButton ); + + format->add_item("Uncompressed",EditorTextureImportPlugin::IMAGE_FORMAT_UNCOMPRESSED); + format->add_item("Compress Lossless (PNG)",EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSLESS); + format->add_item("Compress Lossy (WebP)",EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY); + format->add_item("Compress (VRAM)",EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM); + + + add_margin_child("Texture Format",format); + + quality_vb = memnew( VBoxContainer ); + + HBoxContainer *quality_hb = memnew(HBoxContainer); + HSlider *hs = memnew( HSlider ); + hs->set_h_size_flags(SIZE_EXPAND_FILL); + hs->set_stretch_ratio(0.8); + quality_hb->add_child(hs); + quality_hb->set_h_size_flags(SIZE_EXPAND_FILL); + SpinBox *sb = memnew( SpinBox ); + sb->set_h_size_flags(SIZE_EXPAND_FILL); + sb->set_stretch_ratio(0.2); + quality_hb->add_child(sb); + sb->share(hs); + hs->set_min(0); + hs->set_max(1.0); + hs->set_step(0.01); + hs->set_val(0.7); + quality=hs; + quality_vb->add_margin_child("Texture Compression Quality (WebP):",quality_hb); + + add_child(quality_vb); + + flags = memnew( Tree ); + flags->set_hide_root(true); + TreeItem *root = flags->create_item(); + + + + const char ** fname=flag_names; + + while( *fname ) { + + TreeItem*ti = flags->create_item(root); + ti->set_cell_mode(0,TreeItem::CELL_MODE_CHECK); + ti->set_text(0,*fname); + ti->set_editable(0,true); + items.push_back(ti); + fname++; + } + + + add_margin_child("Texture Options",flags,true); +} + +/////////////////////////////////////////////////////////// + + + + +class EditorTextureImportDialog : public ConfirmationDialog { + + OBJ_TYPE(EditorTextureImportDialog,ConfirmationDialog); + + + EditorImportTextureOptions *texture_options; + + //EditorNode *editor; + + LineEdit *import_path; + LineEdit *save_path; + FileDialog *file_select; + FileDialog *save_file_select; + EditorDirDialog *save_select; + OptionButton *texture_action; + ConfirmationDialog *error_dialog; + CheckButton *crop_source; + bool atlas; + + EditorTextureImportPlugin *plugin; + + void _choose_files(const Vector<String>& p_path); + void _choose_save_dir(const String& p_path); + void _browse(); + void _browse_target(); + void _import(); + + +protected: + + void _notification(int p_what); + static void _bind_methods(); +public: + + Error import(const String& p_from, const String& p_to, const String& p_preset); + void popup_import(const String &p_from=String()); + EditorTextureImportDialog(EditorTextureImportPlugin *p_plugin=NULL,bool p_2d=false,bool p_atlas=false); +}; + + +///////////////////////////////////////////////////////// + + + + +void EditorTextureImportDialog::_choose_files(const Vector<String>& p_path) { + + String files; + for(int i=0;i<p_path.size();i++) { + + if (i>0) + files+=","; + files+=p_path[i]; + } + /* + if (p_path.size()) { + String srctex=p_path[0]; + String ipath = EditorImportDB::get_singleton()->find_source_path(srctex); + + if (ipath!="") + save_path->set_text(ipath.get_base_dir()); + }*/ + import_path->set_text(files); + +} +void EditorTextureImportDialog::_choose_save_dir(const String& p_path) { + + save_path->set_text(p_path); +} + + +void EditorTextureImportDialog::_import() { + + +// ImportMonitorBlock imb; + + Vector<String> files=import_path->get_text().split(","); + + if (!files.size()) { + + error_dialog->set_text("Please specify some files!"); + error_dialog->popup_centered(Size2(200,100)); + return; + } + + String dst_path=save_path->get_text(); + + if (dst_path.empty()) { + + error_dialog->set_text("Please specify a valid target import path!"); + error_dialog->popup_centered(Size2(200,100)); + return; + + } + + if (atlas) { //atlas + + if (files.size()==0) { + + error_dialog->set_text("At least one file needed for Atlas."); + error_dialog->popup_centered(Size2(200,100)); + return; + + } + String dst_file = dst_path; + //dst_file=dst_file.basename()+".tex"; + Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata ); + //imd->set_editor(); + for(int i=0;i<files.size();i++) { + imd->add_source(EditorImportPlugin::validate_source_path(files[i])); + } + imd->set_option("format",texture_options->get_format()); + imd->set_option("flags",texture_options->get_flags()); + imd->set_option("quality",texture_options->get_quality()); + imd->set_option("atlas",true); + imd->set_option("crop",crop_source->is_pressed()); + + Error err = plugin->import(dst_file,imd); + if (err) { + + error_dialog->set_text("Error importing: "+dst_file.get_file()); + error_dialog->popup_centered(Size2(200,100)); + return; + + } + + } else { + + + for(int i=0;i<files.size();i++) { + + String dst_file = dst_path.plus_file(files[i].get_file()); + dst_file=dst_file.basename()+".tex"; + Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata ); + //imd->set_editor(); + imd->add_source(EditorImportPlugin::validate_source_path(files[i])); + imd->set_option("format",texture_options->get_format()); + imd->set_option("flags",texture_options->get_flags()); + imd->set_option("quality",texture_options->get_quality()); + imd->set_option("atlas",false); + Error err = plugin->import(dst_file,imd); + if (err) { + + error_dialog->set_text("Error importing: "+dst_file.get_file()); + error_dialog->popup_centered(Size2(200,100)); + return; + + } + } + } + + hide(); +} + +void EditorTextureImportDialog::_browse() { + + file_select->popup_centered_ratio(); +} + +void EditorTextureImportDialog::_browse_target() { + + if (atlas) { + save_file_select->popup_centered_ratio(); + } else { + save_select->popup_centered_ratio(); + } + +} + + +void EditorTextureImportDialog::popup_import(const String& p_from) { + + popup_centered(Size2(400,400)); + if (p_from!="") { + Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_from); + ERR_FAIL_COND(!rimd.is_valid()); + + save_path->set_text(p_from.get_base_dir()); + texture_options->set_format(EditorTextureImportPlugin::ImageFormat(int(rimd->get_option("format")))); + texture_options->set_flags(rimd->get_option("flags")); + texture_options->set_quality(rimd->get_option("quality")); + String src = ""; + for(int i=0;i<rimd->get_source_count();i++) { + if (i>0) + src+=","; + src+=EditorImportPlugin::expand_source_path(rimd->get_source_path(i)); + } + import_path->set_text(src); + } +} + + +void EditorTextureImportDialog::_notification(int p_what) { + + + if (p_what==NOTIFICATION_ENTER_SCENE) { + + + List<String> extensions; + ImageLoader::get_recognized_extensions(&extensions); + // ResourceLoader::get_recognized_extensions_for_type("PackedTexture",&extensions); + file_select->clear_filters(); + for(int i=0;i<extensions.size();i++) { + + file_select->add_filter("*."+extensions[i]+" ; "+extensions[i].to_upper()); + } + } +} + +Error EditorTextureImportDialog::import(const String& p_from, const String& p_to, const String& p_preset) { + + + import_path->set_text(p_from); + save_path->set_text(p_to); + _import(); + + return OK; +} + +void EditorTextureImportDialog::_bind_methods() { + + + ObjectTypeDB::bind_method("_choose_files",&EditorTextureImportDialog::_choose_files); + ObjectTypeDB::bind_method("_choose_save_dir",&EditorTextureImportDialog::_choose_save_dir); + ObjectTypeDB::bind_method("_import",&EditorTextureImportDialog::_import); + ObjectTypeDB::bind_method("_browse",&EditorTextureImportDialog::_browse); + ObjectTypeDB::bind_method("_browse_target",&EditorTextureImportDialog::_browse_target); +// ADD_SIGNAL( MethodInfo("imported",PropertyInfo(Variant::OBJECT,"scene")) ); +} + +EditorTextureImportDialog::EditorTextureImportDialog(EditorTextureImportPlugin* p_plugin, bool p_2d, bool p_atlas) { + + + atlas=p_atlas; + plugin=p_plugin; + set_title("Import Textures"); + + texture_options = memnew( EditorImportTextureOptions );; + VBoxContainer *vbc = texture_options; + add_child(vbc); + set_child_rect(vbc); + + + VBoxContainer *source_vb=memnew(VBoxContainer); + vbc->add_margin_child("Source Texture(s):",source_vb); + + HBoxContainer *hbc = memnew( HBoxContainer ); + source_vb->add_child(hbc); + + import_path = memnew( LineEdit ); + import_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(import_path); + crop_source = memnew( CheckButton ); + crop_source->set_pressed(true); + source_vb->add_child(crop_source); + crop_source->set_text("Crop empty space."); + if (!p_atlas) + crop_source->hide(); + + Button * import_choose = memnew( Button ); + import_choose->set_text(" .. "); + hbc->add_child(import_choose); + + import_choose->connect("pressed", this,"_browse"); + + hbc = memnew( HBoxContainer ); + vbc->add_margin_child("Target Path:",hbc); + + save_path = memnew( LineEdit ); + save_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(save_path); + + Button * save_choose = memnew( Button ); + save_choose->set_text(" .. "); + hbc->add_child(save_choose); + + save_choose->connect("pressed", this,"_browse_target"); + + file_select = memnew(FileDialog); + file_select->set_access(FileDialog::ACCESS_FILESYSTEM); + add_child(file_select); + file_select->set_mode(FileDialog::MODE_OPEN_FILES); + file_select->connect("files_selected", this,"_choose_files"); + + save_file_select = memnew(FileDialog); + save_file_select->set_access(FileDialog::ACCESS_RESOURCES); + add_child(save_file_select); + save_file_select->set_mode(FileDialog::MODE_SAVE_FILE); + save_file_select->clear_filters(); + save_file_select->add_filter("*.tex;Base Atlas Texture"); + save_file_select->connect("file_selected", this,"_choose_save_dir"); + + save_select = memnew( EditorDirDialog ); + add_child(save_select); + +// save_select->set_mode(FileDialog::MODE_OPEN_DIR); + save_select->connect("dir_selected", this,"_choose_save_dir"); + + get_ok()->connect("pressed", this,"_import"); + get_ok()->set_text("Import"); + + //move stuff up + for(int i=0;i<4;i++) + vbc->move_child( vbc->get_child( vbc->get_child_count() -1), 0); + + error_dialog = memnew ( ConfirmationDialog ); + add_child(error_dialog); + error_dialog->get_ok()->set_text("Accept"); +// error_dialog->get_cancel()->hide(); + + set_hide_on_ok(false); + + if (atlas) { + + texture_options->set_flags(EditorTextureImportPlugin::IMAGE_FLAG_FIX_BORDER_ALPHA|EditorTextureImportPlugin::IMAGE_FLAG_NO_MIPMAPS|EditorTextureImportPlugin::IMAGE_FLAG_FILTER); + texture_options->set_quality(0.7); + texture_options->set_format(EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY); + set_title("Import Textures for Atlas (2D)"); + + } else if (p_2d) { + + texture_options->set_flags(EditorTextureImportPlugin::IMAGE_FLAG_NO_MIPMAPS|EditorTextureImportPlugin::IMAGE_FLAG_FIX_BORDER_ALPHA|EditorTextureImportPlugin::IMAGE_FLAG_FILTER); + texture_options->set_quality(0.7); + texture_options->set_format(EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY); + set_title("Import Textures for 2D"); + } else { + + //texture_options->set_flags(EditorTextureImportPlugin::IMAGE_FLAG_); + //texture_options->set_flags(EditorTextureImportPlugin::IMAGE_FLAG_NO_MIPMAPS); + texture_options->set_flags(EditorTextureImportPlugin::IMAGE_FLAG_FIX_BORDER_ALPHA|EditorTextureImportPlugin::IMAGE_FLAG_FILTER|EditorTextureImportPlugin::IMAGE_FLAG_REPEAT); + texture_options->set_format(EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM); + set_title("Import Textures for 3D"); + } + + +// GLOBAL_DEF("import/shared_textures","res://"); +// Globals::get_singleton()->set_custom_property_info("import/shared_textures",PropertyInfo(Variant::STRING,"import/shared_textures",PROPERTY_HINT_DIR)); + + +} + + + +/////////////////////////////////////////////////////////// + + +String EditorTextureImportPlugin::get_name() const { + + switch(mode) { + case MODE_TEXTURE_2D: { + + return "texture_2d"; + } break; + case MODE_TEXTURE_3D: { + + return "texture_3d"; + + } break; + case MODE_ATLAS: { + + return "texture_atlas"; + } break; + + } + + return ""; + +} +String EditorTextureImportPlugin::get_visible_name() const { + + switch(mode) { + case MODE_TEXTURE_2D: { + + return "2D Texture"; + } break; + case MODE_TEXTURE_3D: { + + return "3D Texture"; + + } break; + case MODE_ATLAS: { + + return "Atlas Teture"; + } break; + + } + + return ""; + +} +void EditorTextureImportPlugin::import_dialog(const String& p_from) { + + dialog->popup_import(p_from); +} + +void EditorTextureImportPlugin::compress_image(EditorExportPlatform::ImageCompression p_mode,Image& image,bool p_smaller) { + + + switch(p_mode) { + case EditorExportPlatform::IMAGE_COMPRESSION_NONE: { + + //do absolutely nothing + + } break; + case EditorExportPlatform::IMAGE_COMPRESSION_INDEXED: { + + //quantize + image.quantize(); + + } break; + case EditorExportPlatform::IMAGE_COMPRESSION_BC: { + + + // for maximum compatibility, BC shall always use mipmaps and be PO2 + image.resize_to_po2(); + if (image.get_mipmaps()==0) + image.generate_mipmaps(); + + image.compress(Image::COMPRESS_BC); + /* + if (has_alpha) { + + if (flags&IMAGE_FLAG_ALPHA_BIT) { + image.convert(Image::FORMAT_BC3); + } else { + image.convert(Image::FORMAT_BC2); + } + } else { + + image.convert(Image::FORMAT_BC1); + }*/ + + + } break; + case EditorExportPlatform::IMAGE_COMPRESSION_PVRTC: + case EditorExportPlatform::IMAGE_COMPRESSION_PVRTC_SQUARE: { + + // for maximum compatibility (hi apple!), PVRT shall always + // use mipmaps, be PO2 and square + + if (image.get_mipmaps()==0) + image.generate_mipmaps(); + image.resize_to_po2(true); + + if (p_smaller) { + + image.compress(Image::COMPRESS_PVRTC2); + //image.convert(has_alpha ? Image::FORMAT_PVRTC2_ALPHA : Image::FORMAT_PVRTC2); + } else { + image.compress(Image::COMPRESS_PVRTC4); + //image.convert(has_alpha ? Image::FORMAT_PVRTC4_ALPHA : Image::FORMAT_PVRTC4); + } + + } break; + case EditorExportPlatform::IMAGE_COMPRESSION_ETC1: { + + image.resize_to_po2(); //square or not? + if (image.get_mipmaps()==0) + image.generate_mipmaps(); + if (!image.detect_alpha()) { + //ETC1 is only opaque + image.compress(Image::COMPRESS_ETC); + } + + } break; + case EditorExportPlatform::IMAGE_COMPRESSION_ETC2: { + + + } break; + } + + +} + +Error EditorTextureImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from) { + + + return import2(p_path,p_from,EditorExportPlatform::IMAGE_COMPRESSION_BC,false); +} + +Error EditorTextureImportPlugin::import2(const String& p_path, const Ref<ResourceImportMetadata>& p_from,EditorExportPlatform::ImageCompression p_compr, bool p_external){ + + + + ERR_FAIL_COND_V(p_from->get_source_count()==0,ERR_INVALID_PARAMETER); + + Ref<ResourceImportMetadata> from=p_from; + + Ref<ImageTexture> texture; + Vector<Ref<AtlasTexture> > atlases; + bool atlas = from->get_option("atlas"); + + int flags=from->get_option("flags"); + uint32_t tex_flags=0; + + if (flags&EditorTextureImportPlugin::IMAGE_FLAG_REPEAT) + tex_flags|=Texture::FLAG_REPEAT; + if (flags&EditorTextureImportPlugin::IMAGE_FLAG_FILTER) + tex_flags|=Texture::FLAG_FILTER; + if (!(flags&EditorTextureImportPlugin::IMAGE_FLAG_NO_MIPMAPS)) + tex_flags|=Texture::FLAG_MIPMAPS; + + int shrink=1; + if (from->has_option("shrink")) + shrink=from->get_option("shrink"); + + if (atlas) { + + //prepare atlas! + Vector< Image > sources; + bool alpha=false; + bool crop = from->get_option("crop"); + + EditorProgress ep("make_atlas","Build Atlas For: "+p_path.get_file(),from->get_source_count()+3); + + print_line("sources: "+itos(from->get_source_count())); + for(int i=0;i<from->get_source_count();i++) { + + String path = EditorImportPlugin::expand_source_path(from->get_source_path(i)); + ep.step("Loading Image: "+path,i); + print_line("source path: "+path); + Image src; + Error err = ImageLoader::load_image(path,&src); + if (err) { + EditorNode::add_io_error("Couldn't load image: "+path); + return err; + } + + if (src.detect_alpha()) + alpha=true; + + sources.push_back(src); + } + ep.step("Converting Images",sources.size()); + + for(int i=0;i<sources.size();i++) { + + if (alpha) { + sources[i].convert(Image::FORMAT_RGBA); + } else { + sources[i].convert(Image::FORMAT_RGB); + } + } + + //texturepacker is not really good for optimizing, so.. + //will at some point likely replace with my own + //first, will find the nearest to a square packing + int border=1; + + Vector<Size2i> src_sizes; + Vector<Rect2> crops; + + ep.step("Cropping Images",sources.size()+1); + + for(int j=0;j<sources.size();j++) { + + Size2i s; + if (crop) { + Rect2 crop = sources[j].get_used_rect(); + print_line("CROP: "+crop); + s=crop.size; + crops.push_back(crop); + } else { + + s=Size2i(sources[j].get_width(),sources[j].get_height()); + } + s+=Size2i(border*2,border*2); + src_sizes.push_back(s); //add a line to constraint width + } + + Vector<Point2i> dst_positions; + Size2i dst_size; + EditorAtlas::fit(src_sizes,dst_positions,dst_size); + + print_line("size that workeD: "+itos(dst_size.width)+","+itos(dst_size.height)); + + ep.step("Blitting Images",sources.size()+2); + + Image atlas; + atlas.create(nearest_power_of_2(dst_size.width),nearest_power_of_2(dst_size.height),0,alpha?Image::FORMAT_RGBA:Image::FORMAT_RGB); + + for(int i=0;i<sources.size();i++) { + + int x=dst_positions[i].x; + int y=dst_positions[i].y; + + Ref<AtlasTexture> at = memnew( AtlasTexture ); + Size2 sz = Size2(sources[i].get_width(),sources[i].get_height()); + if (crop && sz!=crops[i].size) { + Rect2 rect = crops[i]; + rect.size=sz-rect.size; + at->set_region(Rect2(x+border,y+border,crops[i].size.width,crops[i].size.height)); + at->set_margin(rect); + atlas.blit_rect(sources[i],crops[i],Point2(x+border,y+border)); + } else { + at->set_region(Rect2(x+border,y+border,sz.x,sz.y)); + atlas.blit_rect(sources[i],Rect2(0,0,sources[i].get_width(),sources[i].get_height()),Point2(x+border,y+border)); + } + String apath = p_path.get_base_dir().plus_file(from->get_source_path(i).get_file().basename()+".atex"); + print_line("Atlas Tex: "+apath); + at->set_path(apath); + atlases.push_back(at); + + } + if (ResourceCache::has(p_path)) { + texture = Ref<ImageTexture> ( ResourceCache::get(p_path)->cast_to<ImageTexture>() ); + } else { + texture = Ref<ImageTexture>( memnew( ImageTexture ) ); + } + texture->create_from_image(atlas,tex_flags); + + } else { + ERR_FAIL_COND_V(from->get_source_count()!=1,ERR_INVALID_PARAMETER); + + String src_path = EditorImportPlugin::expand_source_path(from->get_source_path(0)); + + if (ResourceCache::has(p_path)) { + Resource *r = ResourceCache::get(p_path); + + texture = Ref<ImageTexture> ( r->cast_to<ImageTexture>() ); + + Image img; + Error err = img.load(src_path); + ERR_FAIL_COND_V(err!=OK,ERR_CANT_OPEN); + texture->create_from_image(img); + } else { + texture=ResourceLoader::load(src_path,"ImageTexture"); + } + + ERR_FAIL_COND_V(texture.is_null(),ERR_CANT_OPEN); + if (!p_external) + from->set_source_md5(0,FileAccess::get_md5(src_path)); + + } + + + int format=from->get_option("format"); + float quality=from->get_option("quality"); + + if (!p_external) { + from->set_editor(get_name()); + texture->set_path(p_path); + texture->set_import_metadata(from); + } + + if (atlas) { + + if (p_external) { + //used by exporter + Array rects(true); + for(int i=0;i<atlases.size();i++) { + rects.push_back(atlases[i]->get_region()); + rects.push_back(atlases[i]->get_margin()); + } + from->set_option("rects",rects); + + } else { + //used by importer + for(int i=0;i<atlases.size();i++) { + String apath = atlases[i]->get_path(); + atlases[i]->set_atlas(texture); + Error err = ResourceSaver::save(apath,atlases[i]); + if (err) { + EditorNode::add_io_error("Couldn't save atlas image: "+apath); + return err; + } + from->set_source_md5(i,FileAccess::get_md5(apath)); + } + } + } + + if (format==IMAGE_FORMAT_COMPRESS_DISK_LOSSLESS || format==IMAGE_FORMAT_COMPRESS_DISK_LOSSY) { + + Image image=texture->get_data(); + ERR_FAIL_COND_V(image.empty(),ERR_INVALID_DATA); + + bool has_alpha=image.detect_alpha(); + if (!has_alpha && image.get_format()==Image::FORMAT_RGBA) { + + image.convert(Image::FORMAT_RGB); + + } + + if (image.get_format()==Image::FORMAT_RGBA && flags&IMAGE_FLAG_FIX_BORDER_ALPHA) { + + image.fix_alpha_edges(); + } + + + if (shrink>1) { + + int orig_w=image.get_width(); + int orig_h=image.get_height(); + image.resize(orig_w/shrink,orig_h/shrink); + texture->create_from_image(image,tex_flags); + texture->set_size_override(Size2(orig_w,orig_h)); + + + } else { + + texture->create_from_image(image,tex_flags); + } + + + if (format==IMAGE_FORMAT_COMPRESS_DISK_LOSSLESS) { + texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSLESS); + } else { + texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); + } + + + + texture->set_lossy_storage_quality(quality); + + Error err = ResourceSaver::save(p_path,texture); + + if (err!=OK) { + EditorNode::add_io_error("Couldn't save converted texture: "+p_path); + return err; + } + + + } else { + + Image image=texture->get_data(); + ERR_FAIL_COND_V(image.empty(),ERR_INVALID_DATA); + + + bool has_alpha=image.detect_alpha(); + if (!has_alpha && image.get_format()==Image::FORMAT_RGBA) { + + image.convert(Image::FORMAT_RGB); + + } + + if (image.get_format()==Image::FORMAT_RGBA && flags&IMAGE_FLAG_FIX_BORDER_ALPHA) { + + image.fix_alpha_edges(); + } + + int orig_w=image.get_width(); + int orig_h=image.get_height(); + + if (shrink>1) { + image.resize(orig_w/shrink,orig_h/shrink); + texture->create_from_image(image,tex_flags); + texture->set_size_override(Size2(orig_w,orig_h)); + } + + if (!(flags&IMAGE_FLAG_NO_MIPMAPS)) { + image.generate_mipmaps(); + + } + + if (format!=IMAGE_FORMAT_UNCOMPRESSED) { + + compress_image(p_compr,image,flags&IMAGE_FLAG_COMPRESS_EXTRA); + } + + + texture->create_from_image(image,tex_flags); + + if (shrink>1) { + texture->set_size_override(Size2(orig_w,orig_h)); + } + + Error err = ResourceSaver::save(p_path,texture); + if (err!=OK) { + EditorNode::add_io_error("Couldn't save converted texture: "+p_path); + return err; + } + + } + + return OK; +} + +Vector<uint8_t> EditorTextureImportPlugin::custom_export(const String& p_path, const Ref<EditorExportPlatform> &p_platform) { + + + Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_path); + + if (rimd.is_null()) { + + StringName group = EditorImportExport::get_singleton()->image_get_export_group(p_path); + + if (group!=StringName()) { + //handled by export group + rimd = Ref<ResourceImportMetadata>( memnew( ResourceImportMetadata ) ); + + int group_format=0; + float group_lossy_quality=EditorImportExport::get_singleton()->image_export_group_get_lossy_quality(group); + int group_shrink=EditorImportExport::get_singleton()->image_export_group_get_shrink(group); + + switch(EditorImportExport::get_singleton()->image_export_group_get_image_action(group)) { + case EditorImportExport::IMAGE_ACTION_NONE: { + + switch(EditorImportExport::get_singleton()->get_export_image_action()) { + case EditorImportExport::IMAGE_ACTION_NONE: { + + group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSLESS; //? + + } break; //use default + case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: { + group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY; + } break; //use default + case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: { + group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM; + } break; //use default + } + + group_lossy_quality=EditorImportExport::get_singleton()->get_export_image_quality(); + + } break; //use default + case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: { + group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_DISK_LOSSY; + } break; //use default + case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: { + group_format=EditorTextureImportPlugin::IMAGE_FORMAT_COMPRESS_RAM; + } break; //use default + } + + + int flags=0; + + if (Globals::get_singleton()->get("texture_import/filter")) + flags|=IMAGE_FLAG_FILTER; + if (!Globals::get_singleton()->get("texture_import/gen_mipmaps")) + flags|=IMAGE_FLAG_NO_MIPMAPS; + if (!Globals::get_singleton()->get("texture_import/repeat")) + flags|=IMAGE_FLAG_REPEAT; + + flags|=IMAGE_FLAG_FIX_BORDER_ALPHA; + + rimd->set_option("format",group_format); + rimd->set_option("flags",flags); + rimd->set_option("quality",group_lossy_quality); + rimd->set_option("atlas",false); + rimd->set_option("shrink",group_shrink); + rimd->add_source(EditorImportPlugin::validate_source_path(p_path)); + + } else if (EditorImportExport::get_singleton()->get_image_formats().has(p_path.extension().to_lower()) && EditorImportExport::get_singleton()->get_export_image_action()!=EditorImportExport::IMAGE_ACTION_NONE) { + //handled by general image export settings + + rimd = Ref<ResourceImportMetadata>( memnew( ResourceImportMetadata ) ); + + switch(EditorImportExport::get_singleton()->get_export_image_action()) { + case EditorImportExport::IMAGE_ACTION_COMPRESS_DISK: rimd->set_option("format",IMAGE_FORMAT_COMPRESS_DISK_LOSSY); break; + case EditorImportExport::IMAGE_ACTION_COMPRESS_RAM: rimd->set_option("format",IMAGE_FORMAT_COMPRESS_RAM); break; + } + + int flags=0; + + if (Globals::get_singleton()->get("texture_import/filter")) + flags|=IMAGE_FLAG_FILTER; + if (!Globals::get_singleton()->get("texture_import/gen_mipmaps")) + flags|=IMAGE_FLAG_NO_MIPMAPS; + if (!Globals::get_singleton()->get("texture_import/repeat")) + flags|=IMAGE_FLAG_REPEAT; + + flags|=IMAGE_FLAG_FIX_BORDER_ALPHA; + + rimd->set_option("flags",flags); + rimd->set_option("quality",EditorImportExport::get_singleton()->get_export_image_quality()); + rimd->set_option("atlas",false); + rimd->add_source(EditorImportPlugin::validate_source_path(p_path)); + + } else { + return Vector<uint8_t>(); + } + } + + int fmt = rimd->get_option("format"); + + if (fmt!=IMAGE_FORMAT_COMPRESS_RAM && fmt!=IMAGE_FORMAT_COMPRESS_DISK_LOSSY) { + print_line("no compress ram or lossy"); + return Vector<uint8_t>(); //pointless to do anything, since no need to reconvert + } + + uint32_t flags = rimd->get_option("flags"); + + MD5_CTX ctx; + uint8_t f4[4]; + encode_uint32(flags,&f4[0]); + uint8_t ic = p_platform->get_image_compression(); + MD5Init(&ctx); + String gp = Globals::get_singleton()->globalize_path(p_path); + CharString cs = gp.utf8(); + MD5Update(&ctx,(unsigned char*)cs.get_data(),cs.length()); + MD5Update(&ctx,f4,4); + MD5Update(&ctx,&ic,1); + + MD5Final(&ctx); + + uint64_t sd=0; + String smd5; + + String md5 = String::md5(ctx.digest); + + String tmp_path = EditorSettings::get_singleton()->get_settings_path().plus_file("tmp/"); + + bool valid=false; + { + //if existing, make sure it's valid + FileAccessRef f = FileAccess::open(tmp_path+"imgexp-"+md5+".txt",FileAccess::READ); + if (f) { + + uint64_t d = f->get_line().strip_edges().to_int64(); + sd = FileAccess::get_modified_time(p_path); + if (d==sd) { + valid=true; + } else { + String cmd5 = f->get_line().strip_edges(); + smd5 = FileAccess::get_md5(p_path); + if (cmd5==smd5) { + valid=true; + } + } + + + } + } + + if (!valid) { + //cache failed, convert + Error err = import2(tmp_path+"imgexp-"+md5+".tex",rimd,p_platform->get_image_compression(),true); + ERR_FAIL_COND_V(err!=OK,Vector<uint8_t>()); + FileAccessRef f = FileAccess::open(tmp_path+"imgexp-"+md5+".txt",FileAccess::WRITE); + + if (sd==0) + sd = FileAccess::get_modified_time(p_path); + if (smd5==String()) + smd5 = FileAccess::get_md5(p_path); + + f->store_line(String::num(sd)); + f->store_line(smd5); + f->store_line(gp); //source path for reference + } + + + Vector<uint8_t> ret; + FileAccessRef f = FileAccess::open(tmp_path+"imgexp-"+md5+".tex",FileAccess::READ); + ERR_FAIL_COND_V(!f,ret); + + ret.resize(f->get_len()); + f->get_buffer(ret.ptr(),ret.size()); + + return ret; +} + + +EditorTextureImportPlugin *EditorTextureImportPlugin::singleton[3]={NULL,NULL,NULL}; + +EditorTextureImportPlugin::EditorTextureImportPlugin(EditorNode *p_editor, Mode p_mode) { + + singleton[p_mode]=this; + editor=p_editor; + mode=p_mode; + dialog = memnew( EditorTextureImportDialog(this,p_mode==MODE_TEXTURE_2D || p_mode==MODE_ATLAS,p_mode==MODE_ATLAS) ); + editor->get_gui_base()->add_child(dialog); + +} diff --git a/tools/editor/io_plugins/editor_texture_import_plugin.h b/tools/editor/io_plugins/editor_texture_import_plugin.h new file mode 100644 index 000000000..1c4df1a4d --- /dev/null +++ b/tools/editor/io_plugins/editor_texture_import_plugin.h @@ -0,0 +1,147 @@ +/*************************************************************************/ +/* editor_texture_import_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_TEXTURE_IMPORT_PLUGIN_H +#define EDITOR_TEXTURE_IMPORT_PLUGIN_H + +#include "tools/editor/editor_import_export.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/tree.h" +#include "scene/gui/label.h" +#include "scene/gui/option_button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/progress_bar.h" +#include "scene/gui/slider.h" +#include "scene/gui/spin_box.h" +#include "tools/editor/editor_file_system.h" +#include "tools/editor/editor_dir_dialog.h" + + + +class EditorNode; +class EditorTextureImportDialog; + +class EditorTextureImportPlugin : public EditorImportPlugin { + + OBJ_TYPE(EditorTextureImportPlugin,EditorImportPlugin); +public: + + + enum Mode { + MODE_TEXTURE_2D, + MODE_TEXTURE_3D, + MODE_ATLAS + }; + + + +private: + Mode mode; + EditorNode *editor; + EditorTextureImportDialog *dialog; + static EditorTextureImportPlugin *singleton[3]; + //used by other importers such as mesh + + + void compress_image(EditorExportPlatform::ImageCompression p_mode,Image& image,bool p_smaller); +public: + + + static EditorTextureImportPlugin *get_singleton(Mode p_mode) { return singleton[p_mode]; } + + enum ImageFormat { + + IMAGE_FORMAT_UNCOMPRESSED, + IMAGE_FORMAT_COMPRESS_DISK_LOSSLESS, + IMAGE_FORMAT_COMPRESS_DISK_LOSSY, + IMAGE_FORMAT_COMPRESS_RAM, + }; + + enum ImageFlags { + + IMAGE_FLAG_STREAM_FORMAT=1, + IMAGE_FLAG_FIX_BORDER_ALPHA=2, + IMAGE_FLAG_ALPHA_BIT=4, //hint for compressions that use a bit for alpha + IMAGE_FLAG_COMPRESS_EXTRA=8, // used for pvrtc2 + IMAGE_FLAG_NO_MIPMAPS=16, //normal for 2D games + IMAGE_FLAG_REPEAT=32, //usually disabled in 2D + IMAGE_FLAG_FILTER=64 //almost always enabled + }; + + virtual String get_name() const; + virtual String get_visible_name() const; + virtual void import_dialog(const String& p_from=""); + virtual Error import(const String& p_path, const Ref<ResourceImportMetadata>& p_from); + virtual Error import2(const String& p_path, const Ref<ResourceImportMetadata>& p_from,EditorExportPlatform::ImageCompression p_compr, bool p_external=false); + virtual Vector<uint8_t> custom_export(const String& p_path,const Ref<EditorExportPlatform> &p_platform); + + + EditorTextureImportPlugin(EditorNode* p_editor=NULL,Mode p_mode=MODE_TEXTURE_2D); +}; + + +class EditorImportTextureOptions : public VBoxContainer { + + OBJ_TYPE( EditorImportTextureOptions, VBoxContainer ); + + + OptionButton *format; + VBoxContainer *quality_vb; + HSlider *quality; + Tree *flags; + Vector<TreeItem*> items; + + bool updating; + + void _changedp(int p_value); + void _changed(); + + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + + + + void set_format(EditorTextureImportPlugin::ImageFormat p_format); + EditorTextureImportPlugin::ImageFormat get_format() const; + + void set_flags(uint32_t p_flags); + uint32_t get_flags() const; + + void set_quality(float p_quality); + float get_quality() const; + + EditorImportTextureOptions(); + + +}; +#endif // EDITOR_TEXTURE_IMPORT_PLUGIN_H diff --git a/tools/editor/io_plugins/editor_translation_import_plugin.cpp b/tools/editor/io_plugins/editor_translation_import_plugin.cpp new file mode 100644 index 000000000..89a7584f7 --- /dev/null +++ b/tools/editor/io_plugins/editor_translation_import_plugin.cpp @@ -0,0 +1,457 @@ +/*************************************************************************/ +/* editor_translation_import_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "editor_translation_import_plugin.h" +#include "scene/gui/file_dialog.h" +#include "tools/editor/editor_dir_dialog.h" +#include "tools/editor/editor_node.h" +#include "tools/editor/property_editor.h" +#include "scene/resources/sample.h" +#include "io/resource_saver.h" +#include "os/file_access.h" +#include "translation.h" +#include "compressed_translation.h" +#include "tools/editor/project_settings.h" + + +class EditorTranslationImportDialog : public ConfirmationDialog { + + OBJ_TYPE(EditorTranslationImportDialog,ConfirmationDialog); + + EditorTranslationImportPlugin *plugin; + + LineEdit *import_path; + LineEdit *save_path; + FileDialog *file_select; + CheckButton *ignore_first; + CheckButton *compress; + CheckButton *add_to_project; + EditorDirDialog *save_select; + ConfirmationDialog *error_dialog; + Vector<TreeItem*> items; + Tree *columns; + +public: + + void _choose_file(const String& p_path) { + + import_path->set_text(p_path); + FileAccess *f = FileAccess::open(p_path,FileAccess::READ); + if (!f) { + + error_dialog->set_text("Invalid source!"); + error_dialog->popup_centered(Size2(200,100)); + return; + + } + + Vector<String> csvh = f->get_csv_line(); + memdelete(f); + + if (csvh.size()<2) { + + error_dialog->set_text("Invalid translation source!"); + error_dialog->popup_centered(Size2(200,100)); + return; + + } + + columns->clear(); + columns->set_columns(2); + TreeItem *root = columns->create_item(); + columns->set_hide_root(true); + columns->set_column_titles_visible(true); + columns->set_column_title(0,"Column"); + columns->set_column_title(1,"Language"); + Vector<String> langs = TranslationServer::get_all_locales(); + Vector<String> names = TranslationServer::get_all_locale_names(); + if (csvh[0]=="") + ignore_first->set_pressed(true); + + + items.clear(); + + for(int i=1;i<csvh.size();i++) { + + TreeItem *ti = columns->create_item(root); + + ti->set_editable(0,true); + ti->set_selectable(0,false); + ti->set_cell_mode(0,TreeItem::CELL_MODE_CHECK); + ti->set_checked(0,true); + ti->set_text(0,itos(i)); + items.push_back(ti); + + String lname = csvh[i].to_lower().strip_edges(); + int idx=-1; + String hint; + for(int j=0;j<langs.size();j++) { + + if (langs[j]==lname.substr(0,langs[j].length()).to_lower()) { + idx=j; + } + if (j>0) { + hint+=","; + } + hint+=names[j].replace(","," "); + } + + ti->set_cell_mode(1,TreeItem::CELL_MODE_RANGE); + ti->set_text(1,hint); + ti->set_editable(1,true); + + + if (idx!=-1) { + ignore_first->set_pressed(true); + ti->set_range(1,idx); + } else { + + //not found, maybe used stupid name + if (lname.begins_with("br")) //brazilian + ti->set_range(1,langs.find("pt")); + else if (lname.begins_with("ch")) //chinese + ti->set_range(1,langs.find("zh")); + else if (lname.begins_with("sp")) //spanish + ti->set_range(1,langs.find("es")); + else if (lname.begins_with("kr"))// kprean + ti->set_range(1,langs.find("ko")); + else if (i==0) + ti->set_range(1,langs.find("en")); + else + ti->set_range(1,langs.find("es")); + } + + ti->set_metadata(1,names[ti->get_range(1)]); + } + + + + } + void _choose_save_dir(const String& p_path) { + + save_path->set_text(p_path); + } + + void _browse() { + + file_select->popup_centered_ratio(); + } + + void _browse_target() { + + save_select->popup_centered_ratio(); + + } + + + void popup_import(const String& p_from) { + + popup_centered(Size2(400,400)); + + if (p_from!="") { + + Ref<ResourceImportMetadata> rimd = ResourceLoader::load_import_metadata(p_from); + ERR_FAIL_COND(!rimd.is_valid()); + ERR_FAIL_COND(rimd->get_source_count()!=1); + _choose_file(EditorImportPlugin::expand_source_path(rimd->get_source_path(0))); + _choose_save_dir(p_from.get_base_dir()); + String locale = rimd->get_option("locale"); + bool skip_first=rimd->get_option("skip_first"); + bool compressed = rimd->get_option("compress"); + + int idx=-1; + + for(int i=0;i<items.size();i++) { + + String il = TranslationServer::get_all_locales()[items[i]->get_range(1)]; + if (il==locale) { + idx=i; + break; + } + } + + if (idx!=-1) { + idx=rimd->get_option("index"); + } + + for(int i=0;i<items.size();i++) { + + if (i==idx) { + + Vector<String> locs = TranslationServer::get_all_locales(); + for(int j=0;j<locs.size();j++) { + if (locs[j]==locale) { + items[i]->set_range(1,j); + } + + } + items[i]->set_checked(0,true); + } else { + items[i]->set_checked(0,false); + + } + } + + ignore_first->set_pressed(skip_first); + compress->set_pressed(compressed); + + + + } + + } + + + void _import() { + + + if (items.size()==0) { + error_dialog->set_text("No items to import!"); + error_dialog->popup_centered(Size2(200,100)); + } + + if (!save_path->get_text().begins_with("res://")) { + error_dialog->set_text("No target path!!"); + error_dialog->popup_centered(Size2(200,100)); + } + + EditorProgress progress("import_xl","Import Translations",items.size()); + for(int i=0;i<items.size();i++) { + + progress.step(items[i]->get_metadata(1),i); + if (!items[i]->is_checked(0)) + continue; + + String locale = TranslationServer::get_all_locales()[items[i]->get_range(1)]; + Ref<ResourceImportMetadata> imd = memnew( ResourceImportMetadata ); + imd->add_source(EditorImportPlugin::validate_source_path(import_path->get_text())); + imd->set_option("locale",locale); + imd->set_option("index",i); + imd->set_option("skip_first",ignore_first->is_pressed()); + imd->set_option("compress",compress->is_pressed()); + + String savefile = save_path->get_text().plus_file(import_path->get_text().get_file().basename()+"."+locale+".xl"); + Error err = plugin->import(savefile,imd); + if (err!=OK) { + error_dialog->set_text("Couldnt import!"); + error_dialog->popup_centered(Size2(200,100)); + } else if (add_to_project->is_pressed()) { + + ProjectSettings::get_singleton()->add_translation(savefile); + } + } + hide(); + + } + + + void _notification(int p_what) { + + + if (p_what==NOTIFICATION_ENTER_SCENE) { + + + } + } + + static void _bind_methods() { + + + ObjectTypeDB::bind_method("_choose_file",&EditorTranslationImportDialog::_choose_file); + ObjectTypeDB::bind_method("_choose_save_dir",&EditorTranslationImportDialog::_choose_save_dir); + ObjectTypeDB::bind_method("_import",&EditorTranslationImportDialog::_import); + ObjectTypeDB::bind_method("_browse",&EditorTranslationImportDialog::_browse); + ObjectTypeDB::bind_method("_browse_target",&EditorTranslationImportDialog::_browse_target); + // ADD_SIGNAL( MethodInfo("imported",PropertyInfo(Variant::OBJECT,"scene")) ); + } + + EditorTranslationImportDialog(EditorTranslationImportPlugin *p_plugin) { + + plugin=p_plugin; + + + set_title("Import Translation"); + + VBoxContainer *vbc = memnew( VBoxContainer ); + add_child(vbc); + set_child_rect(vbc); + + + + VBoxContainer *csvb = memnew( VBoxContainer ); + + HBoxContainer *hbc = memnew( HBoxContainer ); + csvb->add_child(hbc); + vbc->add_margin_child("Source CSV:",csvb); + + import_path = memnew( LineEdit ); + import_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(import_path); + ignore_first = memnew( CheckButton ); + ignore_first->set_text("Ignore First Row"); + csvb->add_child(ignore_first); + + Button * import_choose = memnew( Button ); + import_choose->set_text(" .. "); + hbc->add_child(import_choose); + + import_choose->connect("pressed", this,"_browse"); + + VBoxContainer *tcomp = memnew( VBoxContainer); + hbc = memnew( HBoxContainer ); + tcomp->add_child(hbc); + vbc->add_margin_child("Target Path:",tcomp); + + save_path = memnew( LineEdit ); + save_path->set_h_size_flags(SIZE_EXPAND_FILL); + hbc->add_child(save_path); + + Button * save_choose = memnew( Button ); + save_choose->set_text(" .. "); + hbc->add_child(save_choose); + + save_choose->connect("pressed", this,"_browse_target"); + + compress = memnew( CheckButton); + compress->set_pressed(true); + compress->set_text("Compress"); + tcomp->add_child(compress); + + add_to_project = memnew( CheckButton); + add_to_project->set_pressed(true); + add_to_project->set_text("Add to Project (engine.cfg)"); + tcomp->add_child(add_to_project); + + file_select = memnew(FileDialog); + file_select->set_access(FileDialog::ACCESS_FILESYSTEM); + add_child(file_select); + file_select->set_mode(FileDialog::MODE_OPEN_FILE); + file_select->connect("file_selected", this,"_choose_file"); + file_select->add_filter("*.csv ; Translation CSV"); + save_select = memnew( EditorDirDialog ); + add_child(save_select); + + // save_select->set_mode(FileDialog::MODE_OPEN_DIR); + save_select->connect("dir_selected", this,"_choose_save_dir"); + + get_ok()->connect("pressed", this,"_import"); + get_ok()->set_text("Import"); + + + error_dialog = memnew ( ConfirmationDialog ); + add_child(error_dialog); + error_dialog->get_ok()->set_text("Accept"); + // error_dialog->get_cancel()->hide(); + + set_hide_on_ok(false); + + columns = memnew( Tree ); + vbc->add_margin_child("Import Languages:",columns,true); + } + + ~EditorTranslationImportDialog() { + + } + +}; + + +String EditorTranslationImportPlugin::get_name() const { + + return "translation"; +} +String EditorTranslationImportPlugin::get_visible_name() const { + + return "Translation"; +} +void EditorTranslationImportPlugin::import_dialog(const String& p_from) { + + dialog->popup_import(p_from); +} + +Error EditorTranslationImportPlugin::import(const String& p_path, const Ref<ResourceImportMetadata>& p_from) { + + Ref<ResourceImportMetadata> from = p_from; + ERR_FAIL_COND_V( from->get_source_count()!=1, ERR_INVALID_PARAMETER); + + String source = EditorImportPlugin::expand_source_path( from->get_source_path(0) ); + + FileAccessRef f = FileAccess::open(source,FileAccess::READ); + + ERR_FAIL_COND_V( !f, ERR_INVALID_PARAMETER ); + + bool first=false; + bool skip_first = from->get_option("skip_first"); + int index = from->get_option("index"); + index+=1; + String locale = from->get_option("locale"); + + Ref<Translation> translation = memnew( Translation ); + + translation->set_locale( locale ); + + Vector<String> line = f->get_csv_line(); + + while(line.size()>1) { + + + if (!skip_first) { + ERR_FAIL_INDEX_V(index,line.size(),ERR_INVALID_DATA ); + translation->add_message(line[0].strip_edges(),line[index]); + + } else { + + skip_first=false; + } + + line = f->get_csv_line(); + } + + from->set_source_md5(0,FileAccess::get_md5(source)); + from->set_editor(get_name()); + + String dst_path = p_path; + + if (from->get_option("compress")) { + + Ref<PHashTranslation> cxl = memnew( PHashTranslation ); + cxl->generate( translation ); + translation=cxl; + } + + translation->set_import_metadata(from); + return ResourceSaver::save(dst_path,translation); + +} + + +EditorTranslationImportPlugin::EditorTranslationImportPlugin(EditorNode* p_editor) { + + dialog = memnew(EditorTranslationImportDialog(this)); + p_editor->get_gui_base()->add_child(dialog); +} diff --git a/tools/editor/io_plugins/editor_translation_import_plugin.h b/tools/editor/io_plugins/editor_translation_import_plugin.h new file mode 100644 index 000000000..8ea422c24 --- /dev/null +++ b/tools/editor/io_plugins/editor_translation_import_plugin.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* editor_translation_import_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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_TRANSLATION_IMPORT_PLUGIN_H +#define EDITOR_TRANSLATION_IMPORT_PLUGIN_H + +#include "tools/editor/editor_import_export.h" +#include "scene/resources/font.h" + +class EditorNode; +class EditorTranslationImportDialog; + +class EditorTranslationImportPlugin : public EditorImportPlugin { + + OBJ_TYPE(EditorTranslationImportPlugin,EditorImportPlugin); + + EditorTranslationImportDialog *dialog; +public: + + virtual String get_name() const; + virtual String get_visible_name() const; + virtual void import_dialog(const String& p_from=""); + virtual Error import(const String& p_path, const Ref<ResourceImportMetadata>& p_from); + + + EditorTranslationImportPlugin(EditorNode* p_editor); +}; + +#endif // EDITOR_TRANSLATION_IMPORT_PLUGIN_H |
