From adb4963102e226ff8121caa45a802b7d31c25a81 Mon Sep 17 00:00:00 2001 From: sfb Date: Thu, 21 Oct 2010 22:34:26 -0500 Subject: [PATCH] Added: Object Viewer Qt tool. Thank you dnk-88! --- code/nel/tools/3d/CMakeLists.txt | 1 + .../tools/3d/object_viewer_qt/CMakeLists.txt | 19 + code/nel/tools/3d/object_viewer_qt/COPYING | 1 + code/nel/tools/3d/object_viewer_qt/README | 1 + .../3d/object_viewer_qt/data/andbasr.ttf | Bin 0 -> 250908 bytes .../object_viewer_qt/data/object_viewer.cfg | 29 + .../data/object_viewer_default.cfg | 156 ++ .../doc/html/cdirection_widget.png | Bin 0 -> 3579 bytes .../doc/html/cedit_color_widget.png | Bin 0 -> 12720 bytes .../doc/html/cedit_range_float_widget.png | Bin 0 -> 2104 bytes .../doc/html/cedit_range_int_widget.png | Bin 0 -> 1552 bytes .../object_viewer_qt/doc/html/gui_struct.png | Bin 0 -> 134193 bytes .../doc/html/object_viewer_qt_window.png | Bin 0 -> 191438 bytes .../3d/object_viewer_qt/doc/make_ovqt_dox.bat | 11 + .../3d/object_viewer_qt/doc/make_ovqt_dox.sh | 9 + .../tools/3d/object_viewer_qt/doc/ovqt.dox | 1600 +++++++++++++++++ .../3d/object_viewer_qt/src/CMakeLists.txt | 71 + .../object_viewer_qt/src/animation_dialog.cpp | 190 ++ .../object_viewer_qt/src/animation_dialog.h | 91 + .../3d/object_viewer_qt/src/animation_form.ui | 392 ++++ .../src/animation_set_dialog.cpp | 293 +++ .../src/animation_set_dialog.h | 86 + .../src/animation_set_form.ui | 480 +++++ .../3d/object_viewer_qt/src/attrib_form.ui | 291 +++ .../3d/object_viewer_qt/src/attrib_widget.cpp | 972 ++++++++++ .../3d/object_viewer_qt/src/attrib_widget.h | 362 ++++ .../object_viewer_qt/src/auto_lod_dialog.cpp | 88 + .../3d/object_viewer_qt/src/auto_lod_dialog.h | 73 + .../3d/object_viewer_qt/src/auto_lod_form.ui | 151 ++ .../object_viewer_qt/src/basic_edit_form.ui | 214 +++ .../src/basic_edit_widget.cpp | 192 ++ .../object_viewer_qt/src/basic_edit_widget.h | 61 + .../3d/object_viewer_qt/src/bin_op_dialog.cpp | 63 + .../3d/object_viewer_qt/src/bin_op_dialog.h | 152 ++ .../3d/object_viewer_qt/src/callback.cpp | 27 + .../tools/3d/object_viewer_qt/src/callback.h | 320 ++++ .../object_viewer_qt/src/color_edit_form.ui | 264 +++ .../src/color_edit_widget.cpp | 167 ++ .../object_viewer_qt/src/color_edit_widget.h | 101 ++ .../3d/object_viewer_qt/src/configuration.cpp | 222 +++ .../3d/object_viewer_qt/src/configuration.h | 93 + .../src/constraint_mesh_form.ui | 458 +++++ .../src/constraint_mesh_widget.cpp | 220 +++ .../src/constraint_mesh_widget.h | 80 + .../3d/object_viewer_qt/src/curve_dialog.cpp | 158 ++ .../3d/object_viewer_qt/src/curve_dialog.h | 83 + .../3d/object_viewer_qt/src/curve_form.ui | 253 +++ .../object_viewer_qt/src/day_night_dialog.cpp | 48 + .../object_viewer_qt/src/day_night_dialog.h | 56 + .../3d/object_viewer_qt/src/day_night_form.ui | 47 + .../3d/object_viewer_qt/src/description.h | 145 ++ .../3d/object_viewer_qt/src/direction_form.ui | 178 ++ .../object_viewer_qt/src/direction_widget.cpp | 217 +++ .../object_viewer_qt/src/direction_widget.h | 78 + .../tools/3d/object_viewer_qt/src/dup_ps.cpp | 161 ++ .../tools/3d/object_viewer_qt/src/dup_ps.h | 52 + .../src/edit_range_float_form.ui | 108 ++ .../src/edit_range_uint_form.ui | 108 ++ .../src/edit_range_widget.cpp | 363 ++++ .../object_viewer_qt/src/edit_range_widget.h | 290 +++ .../3d/object_viewer_qt/src/emitter_form.ui | 293 +++ .../3d/object_viewer_qt/src/emitter_page.cpp | 372 ++++ .../3d/object_viewer_qt/src/emitter_page.h | 168 ++ .../tools/3d/object_viewer_qt/src/entity.cpp | 430 +++++ .../tools/3d/object_viewer_qt/src/entity.h | 259 +++ .../src/follow_path_dialog.cpp | 81 + .../object_viewer_qt/src/follow_path_dialog.h | 63 + .../src/global_wind_dialog.cpp | 70 + .../object_viewer_qt/src/global_wind_dialog.h | 59 + .../object_viewer_qt/src/global_wind_form.ui | 71 + .../src/graphics_info_widget.cpp | 142 ++ .../src/graphics_info_widget.h | 79 + .../src/graphics_viewport.cpp | 157 ++ .../object_viewer_qt/src/graphics_viewport.h | 91 + .../3d/object_viewer_qt/src/hoverpoints.cpp | 408 +++++ .../3d/object_viewer_qt/src/hoverpoints.h | 165 ++ .../3d/object_viewer_qt/src/images/.directory | 3 + .../src/images/CollisionZone.bmp | Bin 0 -> 246 bytes .../object_viewer_qt/src/images/Emitter.bmp | Bin 0 -> 246 bytes .../3d/object_viewer_qt/src/images/Force.bmp | Bin 0 -> 246 bytes .../3d/object_viewer_qt/src/images/Light.bmp | Bin 0 -> 246 bytes .../object_viewer_qt/src/images/Located.bmp | Bin 0 -> 246 bytes .../src/images/LocatedInstance.bmp | Bin 0 -> 246 bytes .../object_viewer_qt/src/images/Particle.bmp | Bin 0 -> 246 bytes .../src/images/ParticleSystem.bmp | Bin 0 -> 246 bytes .../src/images/ParticleSystemNotLoaded.bmp | Bin 0 -> 246 bytes .../3d/object_viewer_qt/src/images/Sound.bmp | Bin 0 -> 246 bytes .../object_viewer_qt/src/images/Workspace.bmp | Bin 0 -> 1334 bytes .../3d/object_viewer_qt/src/images/anim.png | Bin 0 -> 8182 bytes .../object_viewer_qt/src/images/animset.png | Bin 0 -> 9060 bytes .../3d/object_viewer_qt/src/images/clear.png | Bin 0 -> 3911 bytes .../object_viewer_qt/src/images/dqynight.png | Bin 0 -> 7208 bytes .../object_viewer_qt/src/images/go-down.png | Bin 0 -> 2404 bytes .../3d/object_viewer_qt/src/images/go-up.png | Bin 0 -> 2426 bytes .../src/images/ico_bgcolor.png | Bin 0 -> 5327 bytes .../src/images/ico_framedelay.png | Bin 0 -> 6481 bytes .../src/images/ico_skelscale.png | Bin 0 -> 4321 bytes .../src/images/insert-horizontal.png | Bin 0 -> 2108 bytes .../3d/object_viewer_qt/src/images/insert.png | Bin 0 -> 1475 bytes .../object_viewer_qt/src/images/list-add.png | Bin 0 -> 2658 bytes .../src/images/list-remove.png | Bin 0 -> 1592 bytes .../3d/object_viewer_qt/src/images/mixer.png | Bin 0 -> 8943 bytes .../3d/object_viewer_qt/src/images/nel.png | Bin 0 -> 15587 bytes .../src/images/nel_ide_load.png | Bin 0 -> 45864 bytes .../3d/object_viewer_qt/src/images/new.png | Bin 0 -> 2461 bytes .../object_viewer_qt/src/images/open-file.png | Bin 0 -> 1088 bytes .../3d/object_viewer_qt/src/images/pause.png | Bin 0 -> 1145 bytes .../3d/object_viewer_qt/src/images/play.png | Bin 0 -> 1177 bytes .../object_viewer_qt/src/images/pqrticles.png | Bin 0 -> 8318 bytes .../src/images/preferences.png | Bin 0 -> 10286 bytes .../object_viewer_qt/src/images/refresh.png | Bin 0 -> 3743 bytes .../object_viewer_qt/src/images/save-as.png | Bin 0 -> 3792 bytes .../3d/object_viewer_qt/src/images/save.png | Bin 0 -> 2122 bytes .../src/images/seek-backward.png | Bin 0 -> 1207 bytes .../src/images/seek-forward.png | Bin 0 -> 1220 bytes .../src/images/skip-backward.png | Bin 0 -> 1211 bytes .../src/images/skip-forward.png | Bin 0 -> 1209 bytes .../3d/object_viewer_qt/src/images/sound.png | Bin 0 -> 6344 bytes .../3d/object_viewer_qt/src/images/stop.png | Bin 0 -> 1165 bytes .../3d/object_viewer_qt/src/images/time.png | Bin 0 -> 2312 bytes .../3d/object_viewer_qt/src/images/veget.png | Bin 0 -> 6479 bytes .../3d/object_viewer_qt/src/images/water.png | Bin 0 -> 7199 bytes .../3d/object_viewer_qt/src/images/wind.png | Bin 0 -> 10396 bytes .../src/located_bindable_form.ui | 776 ++++++++ .../src/located_bindable_page.cpp | 686 +++++++ .../src/located_bindable_page.h | 260 +++ .../3d/object_viewer_qt/src/located_form.ui | 233 +++ .../3d/object_viewer_qt/src/located_page.cpp | 264 +++ .../3d/object_viewer_qt/src/located_page.h | 117 ++ .../tools/3d/object_viewer_qt/src/main.cpp | 101 ++ .../3d/object_viewer_qt/src/main_window.cpp | 636 +++++++ .../3d/object_viewer_qt/src/main_window.h | 136 ++ .../3d/object_viewer_qt/src/mesh_form.ui | 87 + .../3d/object_viewer_qt/src/mesh_widget.cpp | 205 +++ .../3d/object_viewer_qt/src/mesh_widget.h | 76 + .../tools/3d/object_viewer_qt/src/modules.cpp | 52 + .../tools/3d/object_viewer_qt/src/modules.h | 55 + .../src/morph_mesh_dialog.cpp | 241 +++ .../object_viewer_qt/src/morph_mesh_dialog.h | 85 + .../object_viewer_qt/src/morph_mesh_form.ui | 256 +++ .../object_viewer_qt/src/multi_tex_dialog.cpp | 197 ++ .../object_viewer_qt/src/multi_tex_dialog.h | 89 + .../3d/object_viewer_qt/src/multi_tex_form.ui | 290 +++ .../3d/object_viewer_qt/src/object_viewer.cpp | 452 +++++ .../3d/object_viewer_qt/src/object_viewer.h | 227 +++ .../object_viewer_qt/src/object_viewer_qt.qrc | 52 + .../src/particle_control_dialog.cpp | 216 +++ .../src/particle_control_dialog.h | 79 + .../src/particle_control_form.ui | 401 +++++ .../object_viewer_qt/src/particle_editor.cpp | 575 ++++++ .../3d/object_viewer_qt/src/particle_editor.h | 220 +++ .../src/particle_force_form.ui | 199 ++ .../src/particle_force_page.cpp | 223 +++ .../src/particle_force_page.h | 137 ++ .../src/particle_light_form.ui | 114 ++ .../src/particle_light_page.cpp | 74 + .../src/particle_light_page.h | 94 + .../src/particle_link_skeleton_dialog.cpp | 93 + .../src/particle_link_skeleton_dialog.h | 57 + .../src/particle_link_skeleton_form.ui | 50 + .../3d/object_viewer_qt/src/particle_node.cpp | 523 ++++++ .../3d/object_viewer_qt/src/particle_node.h | 295 +++ .../src/particle_property_dialog.cpp | 139 ++ .../src/particle_property_dialog.h | 89 + .../src/particle_sound_form.ui | 189 ++ .../src/particle_sound_page.cpp | 168 ++ .../src/particle_sound_page.h | 109 ++ .../src/particle_system_form.ui | 909 ++++++++++ .../src/particle_system_page.cpp | 667 +++++++ .../src/particle_system_page.h | 156 ++ .../src/particle_texture_anim_form.ui | 110 ++ .../src/particle_texture_anim_widget.cpp | 151 ++ .../src/particle_texture_anim_widget.h | 93 + .../src/particle_texture_form.ui | 107 ++ .../src/particle_texture_widget.cpp | 126 ++ .../src/particle_texture_widget.h | 74 + .../src/particle_tree_model.cpp | 537 ++++++ .../src/particle_tree_model.h | 184 ++ .../src/particle_workspace_dialog.cpp | 767 ++++++++ .../src/particle_workspace_dialog.h | 133 ++ .../src/particle_workspace_form.ui | 36 + .../src/particle_workspace_page.cpp | 197 ++ .../src/particle_workspace_page.h | 68 + .../src/particle_zone_form.ui | 151 ++ .../src/particle_zone_page.cpp | 151 ++ .../object_viewer_qt/src/particle_zone_page.h | 80 + .../object_viewer_qt/src/ps_initial_pos.cpp | 209 +++ .../3d/object_viewer_qt/src/ps_initial_pos.h | 101 ++ .../3d/object_viewer_qt/src/ps_mover_form.ui | 193 ++ .../3d/object_viewer_qt/src/ps_mover_page.cpp | 245 +++ .../3d/object_viewer_qt/src/ps_mover_page.h | 174 ++ .../3d/object_viewer_qt/src/ps_wrapper.h | 118 ++ .../object_viewer_qt/src/settings_dialog.cpp | 373 ++++ .../3d/object_viewer_qt/src/settings_dialog.h | 84 + .../3d/object_viewer_qt/src/settings_form.ui | 657 +++++++ .../object_viewer_qt/src/setup_fog_dialog.cpp | 80 + .../object_viewer_qt/src/setup_fog_dialog.h | 60 + .../3d/object_viewer_qt/src/setup_fog_form.ui | 242 +++ .../src/skeleton_scale_dialog.cpp | 771 ++++++++ .../src/skeleton_scale_dialog.h | 159 ++ .../src/skeleton_scale_form.ui | 633 +++++++ .../src/skeleton_tree_model.cpp | 197 ++ .../src/skeleton_tree_model.h | 109 ++ .../3d/object_viewer_qt/src/slot_form.ui | 249 +++ .../src/slot_manager_dialog.cpp | 284 +++ .../src/slot_manager_dialog.h | 86 + .../3d/object_viewer_qt/src/sound_system.cpp | 224 +++ .../3d/object_viewer_qt/src/sound_system.h | 119 ++ .../object_viewer_qt/src/spinner_dialog.cpp | 68 + .../3d/object_viewer_qt/src/spinner_dialog.h | 73 + .../tools/3d/object_viewer_qt/src/stdpch.cpp | 20 + .../tools/3d/object_viewer_qt/src/stdpch.h | 176 ++ .../object_viewer_qt/src/sun_color_dialog.cpp | 71 + .../object_viewer_qt/src/sun_color_dialog.h | 58 + .../3d/object_viewer_qt/src/sun_color_form.ui | 74 + .../3d/object_viewer_qt/src/tail_form.ui | 131 ++ .../src/tail_particle_widget.cpp | 144 ++ .../src/tail_particle_widget.h | 66 + .../src/value_blender_dialog.cpp | 70 + .../src/value_blender_dialog.h | 200 +++ .../src/value_from_emitter_dialog.cpp | 41 + .../src/value_from_emitter_dialog.h | 109 ++ .../src/value_gradient_dialog.cpp | 395 ++++ .../src/value_gradient_dialog.h | 444 +++++ .../src/vegetable_appearance_page.cpp | 235 +++ .../src/vegetable_appearance_page.h | 71 + .../src/vegetable_apperance_form.ui | 166 ++ .../src/vegetable_density_form.ui | 382 ++++ .../src/vegetable_density_page.cpp | 304 ++++ .../src/vegetable_density_page.h | 77 + .../object_viewer_qt/src/vegetable_dialog.cpp | 322 ++++ .../object_viewer_qt/src/vegetable_dialog.h | 74 + .../src/vegetable_dialog_form.ui | 274 +++ .../object_viewer_qt/src/vegetable_editor.cpp | 739 ++++++++ .../object_viewer_qt/src/vegetable_editor.h | 252 +++ .../src/vegetable_landscape_form.ui | 268 +++ .../src/vegetable_landscape_page.cpp | 135 ++ .../src/vegetable_landscape_page.h | 61 + .../object_viewer_qt/src/vegetable_node.cpp | 130 ++ .../3d/object_viewer_qt/src/vegetable_node.h | 61 + .../src/vegetable_noise_value_form.ui | 168 ++ .../src/vegetable_noise_value_widget.cpp | 108 ++ .../src/vegetable_noise_value_widget.h | 69 + .../src/vegetable_rotate_form.ui | 63 + .../src/vegetable_rotate_page.cpp | 93 + .../src/vegetable_rotate_page.h | 64 + .../src/vegetable_scale_form.ui | 74 + .../src/vegetable_scale_page.cpp | 92 + .../src/vegetable_scale_page.h | 65 + .../src/water_pool_dialog.cpp | 35 + .../object_viewer_qt/src/water_pool_dialog.h | 51 + .../object_viewer_qt/src/water_pool_form.ui | 281 +++ .../3d/object_viewer_qt/src/workspace_form.ui | 456 +++++ 253 files changed, 40248 insertions(+) create mode 100644 code/nel/tools/3d/object_viewer_qt/CMakeLists.txt create mode 100644 code/nel/tools/3d/object_viewer_qt/COPYING create mode 100644 code/nel/tools/3d/object_viewer_qt/README create mode 100644 code/nel/tools/3d/object_viewer_qt/data/andbasr.ttf create mode 100644 code/nel/tools/3d/object_viewer_qt/data/object_viewer.cfg create mode 100644 code/nel/tools/3d/object_viewer_qt/data/object_viewer_default.cfg create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/html/cdirection_widget.png create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/html/cedit_color_widget.png create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/html/cedit_range_float_widget.png create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/html/cedit_range_int_widget.png create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/html/gui_struct.png create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/html/object_viewer_qt_window.png create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.bat create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.sh create mode 100644 code/nel/tools/3d/object_viewer_qt/doc/ovqt.dox create mode 100644 code/nel/tools/3d/object_viewer_qt/src/CMakeLists.txt create mode 100644 code/nel/tools/3d/object_viewer_qt/src/animation_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/animation_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/animation_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/animation_set_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/attrib_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/attrib_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/attrib_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/auto_lod_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/basic_edit_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/callback.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/callback.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/color_edit_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/configuration.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/configuration.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/curve_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/curve_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/curve_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/day_night_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/description.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/direction_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/direction_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/direction_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/dup_ps.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/dup_ps.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/edit_range_float_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/edit_range_uint_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/emitter_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/emitter_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/emitter_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/entity.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/entity.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/global_wind_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/hoverpoints.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/hoverpoints.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/.directory create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/CollisionZone.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/Emitter.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/Force.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/Light.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/Located.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/LocatedInstance.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/Particle.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/ParticleSystem.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/ParticleSystemNotLoaded.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/Sound.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/Workspace.bmp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/anim.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/animset.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/clear.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/dqynight.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/go-down.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/go-up.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/ico_bgcolor.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/ico_framedelay.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/ico_skelscale.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/insert-horizontal.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/insert.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/list-add.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/list-remove.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/mixer.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/nel.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/nel_ide_load.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/new.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/open-file.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/pause.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/play.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/pqrticles.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/preferences.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/refresh.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/save-as.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/save.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/seek-backward.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/seek-forward.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/skip-backward.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/skip-forward.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/sound.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/stop.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/time.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/veget.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/water.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/images/wind.png create mode 100644 code/nel/tools/3d/object_viewer_qt/src/located_bindable_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/located_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/located_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/located_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/main.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/main_window.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/main_window.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/mesh_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/mesh_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/mesh_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/modules.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/modules.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/morph_mesh_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/multi_tex_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/object_viewer.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/object_viewer.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/object_viewer_qt.qrc create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_control_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_editor.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_editor.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_force_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_force_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_force_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_light_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_light_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_light_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_node.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_node.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_sound_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_system_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_system_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_system_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_texture_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_workspace_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_zone_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/ps_mover_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/ps_wrapper.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/settings_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/settings_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/settings_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/setup_fog_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/slot_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/sound_system.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/sound_system.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/stdpch.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/stdpch.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/sun_color_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/tail_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_apperance_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_density_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_node.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_node.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.cpp create mode 100644 code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.h create mode 100644 code/nel/tools/3d/object_viewer_qt/src/water_pool_form.ui create mode 100644 code/nel/tools/3d/object_viewer_qt/src/workspace_form.ui diff --git a/code/nel/tools/3d/CMakeLists.txt b/code/nel/tools/3d/CMakeLists.txt index 412ef130c..7c0d02fec 100644 --- a/code/nel/tools/3d/CMakeLists.txt +++ b/code/nel/tools/3d/CMakeLists.txt @@ -46,6 +46,7 @@ ENDIF(WIN32) IF(WITH_QT) ADD_SUBDIRECTORY(tile_edit_qt) + ADD_SUBDIRECTORY(object_viewer_qt) ENDIF(WITH_QT) IF(SQUISH_FOUND) diff --git a/code/nel/tools/3d/object_viewer_qt/CMakeLists.txt b/code/nel/tools/3d/object_viewer_qt/CMakeLists.txt new file mode 100644 index 000000000..9341954ab --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/CMakeLists.txt @@ -0,0 +1,19 @@ +#----------------------------------------------------------------------------- +# +# Object Viewer Qt +# Copyright (C) 2010 Dzmitry Kamiahin +# +#----------------------------------------------------------------------------- + +# This tells the application(s) where to fidn the installed data. +ADD_DEFINITIONS(-DDATA_DIR="\\"${NL_SHARE_PREFIX}/object_viewer_qt/\\"") + +ADD_SUBDIRECTORY(src) + +INSTALL(DIRECTORY data/ + DESTINATION share/object_viewer_qt/data + COMPONENT data + PATTERN "CVS" EXCLUDE + PATTERN ".svn" EXCLUDE + PATTERN "Makefile*" EXCLUDE) + diff --git a/code/nel/tools/3d/object_viewer_qt/COPYING b/code/nel/tools/3d/object_viewer_qt/COPYING new file mode 100644 index 000000000..69ec4c052 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/COPYING @@ -0,0 +1 @@ +Put your license here! \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/README b/code/nel/tools/3d/object_viewer_qt/README new file mode 100644 index 000000000..7a4a570a1 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/README @@ -0,0 +1 @@ +Talk about how to use Hello World or who wrote it, that sort of stuff. \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/data/andbasr.ttf b/code/nel/tools/3d/object_viewer_qt/data/andbasr.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d785cc9df5d995453f3b09819fe96b2b05029c5a GIT binary patch literal 250908 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%0DHbO~WNV3a=DEl?lon6&3t-cd@%^q@ZW^ysNu8!* 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 z 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

6r9Q60f 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%0Ag$kO4;1x+xQXNxl;6BKX!Q`EKRwlEBLp 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ଋ}=TJLMl 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/doc/html/cedit_color_widget.png b/code/nel/tools/3d/object_viewer_qt/doc/html/cedit_color_widget.png new file mode 100644 index 0000000000000000000000000000000000000000..b0e14e5698606bd379dccdaf834d6ae11038de3e GIT binary patch literal 12720 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∓DEg`@jGAh;$r1w z&Gg+(6cN_OMbr^66}Xkt<@tyX zWCBDW0_cbsAT#2j;pb6kk^r7h-s@8}Hi|4p$7_~aOn|*L_vkZ;slauCqhHZ#tSCVY zJ)*B{=SvIkaG^dYgTrr-O3L4C$rk;>vzjah;MEx1!@02_KmTf@`@fx0P0~KlcA3;S zHJhUk|1G3mYTz72yQu|*0VIUu;BQ%^on2V+o=!CQo1 zt7fChGVHx@Ba;vT-qS=ol8NPP4=rculM z$!P5)xFIIMFm zc;Z-wQ>ueH@s+G#RMFxyjNhu4lIpZTAt9vQQZ?fWe*GpLo}rWVM}+6D&M3%sSP^PAI-s#;>c)BvMOKAI8pSdY%vgggkLQDnbmMmy zPyA}W!+PX!YO$)6z22}7YpopyGg`0aPJ;du8q?<>fu?~X!N(-NGTyzfd~&^C*BYvh zW+SbtGh!n^&_ez6aSfy_p46piu@8T9#9{LjiWkZ(p;E&5z2gx{|J*_T z$u`rRW8koR{ipBxkdUDX^H6L6M@@J$UJPPM=r?eH^N0^zC- zkHt@V5qbxCn1{!`IKtqp3zE#sjrITS>3~5{QzCfk*SpKtYc0Mv3x=4o@V`?46l7Ip JYNSm={txa|i^Ko` literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/doc/html/cedit_range_float_widget.png b/code/nel/tools/3d/object_viewer_qt/doc/html/cedit_range_float_widget.png new file mode 100644 index 0000000000000000000000000000000000000000..3c612633e6162a999d6bc022a6b746c0346755db GIT binary patch literal 2104 zcmV-82*>w{P)C{3&urWFf zqDat6v@0NOY~3m}sxeAb#xw#1y0NJ+Dkux?BW+$duR4k2*goHL|2#(Qo<=KqLeiL@pypGB1KFxzjACthv?1`K%wA`!C{yk7BMVqNKzBIZDF8k5{13~E90s@?9$Bw%{F&FgPj`mk z(O;bp;6YAttf5WKXe8?xgBS}0E_Ph1u3l@m+fz|YhT!x0+S@-_U9rk+F{|B|bR$X_ z99di(5G8^+aJQQchsVbQQz0u2p8Rg*{l9C!I1sVZM4<)8iG^=So0`#1)-eXr+tX89 zTf1V#N`t|W#_lZ@PN$P3Nsp(!x~fX;zN8yy5W_6??{4Afj#4%hhCdWhK{*@>{bF(M&UZLoI?GeRn2H8>llMz7{+8W#jYTSK0UI%@!;FtynW5aA2c1< zwcd&+i6(B6R$w~0;qeP#s%qc`PL|}kQBjgUUndomG&A}AzScdPpS$zqpRb=D*k+B8 z=A#+KGbMcfj}Pr^e)YeByqboGk2GyvK}Q#6LhsR=>-T&docv&|fAvhu)Sj8Mqtqc@ z*2%77$o~H{AB!};^wIarE9f{%M0XaWrugMB15MJ}f<4-y6-1+h6)@?VU57 z$aNucoiFVAjqUNa3w0L%zkaplCr9f~Y%d{a;-TKwVC^x_Uv6@_Oj6i5gB~9$56k@99;q_?x+kSnL+Sl=YzTp^u#b#XD%ZHZKE89 zQ3<11!X}xD*X!-*=uo>Civ}kVogp|vUKI|70j^M(bKJzC@|^RqJpmFI=dMt(vcgs5 zTFADUH9!B`TgCbhHXn=9ea;JsF52V@w0W+4b-gv!$|^$%j()*rsW zuimo%w@r7HP|7;B;DZ82P6`YV3@!$FIcYRulT3x@g}S=0==EgsRkMK*C)2$` z>lG1>mRXjay8=l~WLoL2tX-}%kh8C1@I2So*T2AR6UGt;JiB8T=Dd)|lCAqU+;V&6 zp|A`X_l}mo7e`Bp^|lU}zjg5V6LsdXGl%bf;FbCpx4LvnJ1WCSW)MJc(!rl2cw~Sq zMr6XM2T7v|n`9~uhn?ei_)@zgi$j>lA`y59bdur}<^QKABaui5dWllf4u@kgJCyT6 z;yRB$a%$6Y&s*!wqwoFlJ3o8zzE`%*3^5EfPoI8&>aC+<9eMfLp72&zHe3TqW)P&+ zZH#n7C%5T^U>9d}oAt?}AYqeCMR6)VdjXtGQ38gE0N{#q5Mc7|Ns6Kj289Y@@yU51 zvHkD(Y(K5aCkf2?#ZA`!Ltb`UiDEq}OF(bdsdZuRDoJJ#RN;-~BTuxP{q}ujmrfk_ z6+co)C5wWDO%Q1(BMS4J7ZP1~gG)a9skipJg0QE}@A%rX=-#sJk8S*s@$kt9YUt7P zr?GY4TahIf-;@5cmZfI*ZFFP9+WoAt>>Im(f1MTn*o^)2_8Xe_wY6MtQHm-yL8yp~ zWq?cMx{%1S#-}zvv~%N{_JFzS)_uQzu#AdM(7orW+wR}-<}fHi&RI49BHOzAV`F*Sv;@ zv!O8RZWbpT-ExeqRrFyISVje1FpQ8NyJcgPQ;22z*$Q)A|(8AUQuNb}T;M$VKN zgPTyP6wHxsL z>GP>dfbo*l{8}H3!gX|bN)9n!%_x$2LYk*$v~#A!7=+Dk9~l|xyxfsr;IvxvaHSh; zvI1EC`Te6qg9F3EBQ~2&?e3@mfn!b!?k*xvog01ZWo*Zd4wu~kiv{QseZ!$=-t-Bf zQMa3c>k##bTtWmvU>JrZwOB#Gl0lEls|rX# z5ZilvyN`_xveS3=Icc`0fUjD~z$i(8)CvfVYDPO*&z^&zCP_=L5KD`W0455*rr1zm zBr3na`-9_C%dJLonb}lirzjmP9~NOl+SH6jvPRFO%w?cQz~Z;GfGKj&k~lpFffKN} z0K~+T=G5&SA`3tUk>kLz22NlT&y1RVqcXAwL~@WpAP|VSAcH_45OF~Ufj}VQf(!zI iK*R+Z1OkDG%l`l@&iYyshhiB30000^WO0089T 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

Lc_($> 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¨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 {Q5lVd$I46GjqQv$J9}{QS!3#1 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

qTrVrZM=$R~C$bBK44PhKZ)p;5NnB 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%0AVyuhFR-tQUS=ziZ1eUBdw&oTsEZh9Pt$UWs{ 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 V3S$F_dQ1QS002ovPDHLkV1jXg(MbRR literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.bat b/code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.bat new file mode 100644 index 000000000..5b2ee7a03 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.bat @@ -0,0 +1,11 @@ +@echo off + +del html\ovqt\*.* /Q +set WORKDIR=%CD% + +cd .. +set CURDIR=%CD% +cd %WORKDIR% +doxygen ovqt.dox -DCURDIR + +pause diff --git a/code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.sh b/code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.sh new file mode 100644 index 000000000..d39c1a548 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/doc/make_ovqt_dox.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +rm -rf html/ovqt +WORKDIR=$(pwd) + +cd .. +export CURDIR=$(pwd) +cd $WORKDIR +doxygen ovqt.dox -DCURDIR diff --git a/code/nel/tools/3d/object_viewer_qt/doc/ovqt.dox b/code/nel/tools/3d/object_viewer_qt/doc/ovqt.dox new file mode 100644 index 000000000..e15e37e5a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/doc/ovqt.dox @@ -0,0 +1,1600 @@ +# Doxyfile 1.6.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = "Object Viewer Qt" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = r90 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set +# FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = YES + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvances is that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = YES + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/code/nel/tools/3d/object_viewer_qt/src/CMakeLists.txt b/code/nel/tools/3d/object_viewer_qt/src/CMakeLists.txt new file mode 100644 index 000000000..abaa6fc30 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/CMakeLists.txt @@ -0,0 +1,71 @@ +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${LIBXML2_INCLUDE_DIR} ${NEL_INCLUDE_DIR} ${QT_INCLUDES}) +INCLUDE( ${QT_USE_FILE} ) + +FILE(GLOB OBJECT_VIEWER_SRC configuration.h entity.h object_viewer.h particle_editor.h modules.h sound_system.h + particle_node.h ps_initial_pos.h dup_ps.h vegetable_editor.h vegetable_node.h *.cpp) +SET(OBJECT_VIEWER_HDR main_window.h graphics_viewport.h animation_dialog.h + animation_set_dialog.h settings_dialog.h setup_fog_dialog.h + slot_manager_dialog.h particle_control_dialog.h particle_workspace_dialog.h + particle_tree_model.h particle_system_page.h particle_workspace_page.h edit_range_widget.h + emitter_page.h attrib_widget.h located_bindable_page.h located_page.h + particle_force_page.h particle_light_page.h particle_zone_page.h particle_sound_page.h + basic_edit_widget.h direction_widget.h color_edit_widget.h particle_property_dialog.h + ps_mover_page.h graphics_info_widget.h value_blender_dialog.h value_gradient_dialog.h + value_from_emitter_dialog.h curve_dialog.h bin_op_dialog.h hoverpoints.h + mesh_widget.h morph_mesh_dialog.h constraint_mesh_widget.h tail_particle_widget.h + auto_lod_dialog.h particle_texture_widget.h particle_texture_anim_widget.h + multi_tex_dialog.h spinner_dialog.h follow_path_dialog.h water_pool_dialog.h + skeleton_scale_dialog.h skeleton_tree_model.h particle_link_skeleton_dialog.h + vegetable_dialog.h global_wind_dialog.h day_night_dialog.h sun_color_dialog.h + vegetable_noise_value_widget.h vegetable_density_page.h vegetable_landscape_page.h + vegetable_scale_page.h vegetable_appearance_page.h vegetable_rotate_page.h) +SET(OBJECT_VIEWER_UIS animation_form.ui animation_set_form.ui settings_form.ui + setup_fog_form.ui slot_form.ui particle_control_form.ui particle_workspace_form.ui + edit_range_float_form.ui edit_range_uint_form.ui particle_system_form.ui workspace_form.ui + attrib_form.ui emitter_form.ui located_bindable_form.ui located_form.ui + particle_force_form.ui particle_light_form.ui particle_zone_form.ui particle_sound_form.ui + basic_edit_form.ui direction_form.ui color_edit_form.ui ps_mover_form.ui curve_form.ui + mesh_form.ui morph_mesh_form.ui constraint_mesh_form.ui tail_form.ui auto_lod_form.ui + particle_texture_form.ui particle_texture_anim_form.ui multi_tex_form.ui skeleton_scale_form.ui + particle_link_skeleton_form.ui water_pool_form.ui vegetable_dialog_form.ui + vegetable_noise_value_form.ui global_wind_form.ui sun_color_form.ui day_night_form.ui + vegetable_density_form.ui vegetable_apperance_form.ui vegetable_landscape_form.ui + vegetable_rotate_form.ui vegetable_scale_form.ui) +SET(OBJECT_VIEWER_RCS object_viewer_qt.qrc) + +SET(QT_USE_QTGUI TRUE) +SET(QT_USE_QTOPENGL TRUE) + +QT4_ADD_RESOURCES( OBJECT_VIEWER_RC_SRCS ${OBJECT_VIEWER_RCS} ) +QT4_WRAP_CPP( OBJECT_VIEWER_MOC_SRCS ${OBJECT_VIEWER_HDR} ) +QT4_WRAP_UI( OBJECT_VIEWER_UI_HDRS ${OBJECT_VIEWER_UIS} ) + +ADD_EXECUTABLE(object_viewer_qt WIN32 ${OBJECT_VIEWER_SRC} ${OBJECT_VIEWER_MOC_SRCS} ${OBJECT_VIEWER_RC_SRCS} ${OBJECT_VIEWER_UI_HDRS}) + +TARGET_LINK_LIBRARIES(object_viewer_qt + ${PLATFORM_LINKFLAGS} + ${LIBXML2_LIBRARIES} + ${PNG_LIBRARIES} + ${FREETYPE_LIBRARY} + ${JPEG_LIBRARY} + nelmisc + nel3d + nelsound + nelsnd_lowlevel + nelligo + nelgeorges + ${QT_LIBRARIES} + ${QT_QTOPENGL_LIBRARY} + ${QT_QTMAIN_LIBRARY}) + +ADD_DEFINITIONS(-DQT_NO_KEYWORDS ${LIBXML2_DEFINITIONS} ${QT_DEFINITIONS}) +NL_DEFAULT_PROPS(object_viewer_qt "NeL, Tools, 3D: Object Viewer Qt") +NL_ADD_RUNTIME_FLAGS(object_viewer_qt) + + + +IF(WITH_PCH) + ADD_NATIVE_PRECOMPILED_HEADER(object_viewer_qt ${CMAKE_CURRENT_SOURCE_DIR}/stdpch.h ${CMAKE_CURRENT_SOURCE_DIR}/stdpch.cpp) +ENDIF(WITH_PCH) + +INSTALL(TARGETS object_viewer_qt RUNTIME DESTINATION bin COMPONENT runtime) diff --git a/code/nel/tools/3d/object_viewer_qt/src/animation_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/animation_dialog.cpp new file mode 100644 index 000000000..238af812c --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/animation_dialog.cpp @@ -0,0 +1,190 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "animation_dialog.h" + +// Qt includes + +// NeL includes + +// Project includes +#include "modules.h" + +using namespace NLMISC; + +namespace NLQT { + +CAnimationDialog::CAnimationDialog(QWidget *parent) + : QDockWidget(parent) +{ + _ui.setupUi(this); + + //setFixedHeight(sizeHint().height()); + + connect(_ui.startPushButton, SIGNAL(clicked()), this, SLOT(start())); + connect(_ui.playPushButton, SIGNAL(clicked()), this, SLOT(play())); + connect(_ui.stopPushButton, SIGNAL(clicked()), this, SLOT(stop())); + connect(_ui.endPushButton, SIGNAL(clicked()), this, SLOT(end())); + connect(_ui.horizontalSlider, SIGNAL(sliderMoved(int)), this, SLOT(changeFrame(int))); + connect(_ui.startSpinBox, SIGNAL(valueChanged(int)), this, SLOT(changeStartAnim(int))); + connect(_ui.endSpinBox, SIGNAL(valueChanged(int)), this, SLOT(changeEndAnim(int))); + + // init QTimeLine + _timeLine = new QTimeLine(_ui.endSpinBox->value() * _frameRate, this); + _timeLine->setCurveShape(QTimeLine::LinearCurve); + _timeLine->setUpdateInterval(25); + _timeLine->setFrameRange(_ui.startSpinBox->value(), _ui.endSpinBox->value()); + + connect(_timeLine, SIGNAL(frameChanged(int)), this, SLOT(updateAnim(int))); + connect(_timeLine, SIGNAL(finished()), this, SLOT(finish())); + + connect(_ui.incPosCheckBox, SIGNAL(toggled(bool)), this, SLOT(setIncPos(bool))); + connect(_ui.inPlaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(setInPlace(bool))); + + // sync horizontalSlider with a timeLine + _ui.endSpinBox->setValue(99); +} + +CAnimationDialog::~CAnimationDialog() +{ +} + +NL3D::TAnimationTime CAnimationDialog::getTime () +{ + return float(_timeLine->currentFrame()) / _frameRate; +} + +void CAnimationDialog::changeAnimLength() +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + float animLength = entity.getPlayListLength(); + + _ui.startSpinBox->setValue(0); + _ui.endSpinBox->setValue(int(animLength * _frameRate)); +} + +void CAnimationDialog::setCurrentShape(const QString &name) +{ + if (name.isEmpty()) + return; + CEntity &entity = Modules::objView().getEntity(name.toStdString()); + + _ui.inPlaceCheckBox->setChecked(entity.getInPlace()); + _ui.incPosCheckBox->setChecked(entity.getIncPos()); +} + +void CAnimationDialog::start() +{ + _timeLine->setCurrentTime((float(_ui.startSpinBox->value()) / _frameRate) * 1000); +} + +void CAnimationDialog::play() +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + { + _ui.playPushButton->setChecked(false); + return; + } + CEntity &entity = Modules::objView().getEntity(curObj); + entity.playbackAnim(true); + + if (_timeLine->state() == QTimeLine::Running) + _timeLine->setPaused(true); + else if (_timeLine->currentFrame() == _timeLine->endFrame()) _timeLine->start(); + else + _timeLine->resume(); +} + +void CAnimationDialog::stop() +{ + _timeLine->stop(); + _timeLine->setCurrentTime(0); + _ui.playPushButton->setChecked(false); + + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + entity.playbackAnim(false); +} + +void CAnimationDialog::end() +{ + _timeLine->setCurrentTime((float(_ui.endSpinBox->value()) / _frameRate) * 1000); +} + +void CAnimationDialog::changeFrame(int frame) +{ + if (_timeLine->state() == QTimeLine::Running) + { + _timeLine->setPaused(true); + _timeLine->setCurrentTime((float(frame) / _frameRate) * 1000); + _timeLine->resume(); + } + else _timeLine->setCurrentTime((float(frame) / _frameRate) * 1000); +} + +void CAnimationDialog::changeStartAnim(int start) +{ + _timeLine->setDuration((float(start - _ui.startSpinBox->value()) / _frameRate) * 1000); + _timeLine->setFrameRange(start, _ui.endSpinBox->value()); +} + +void CAnimationDialog::changeEndAnim(int end) +{ + _ui.horizontalSlider->setMaximum(end); + _timeLine->setDuration((float(end - _ui.startSpinBox->value()) / _frameRate) * 1000); + _timeLine->setFrameRange(_ui.startSpinBox->value(), end); +} + +void CAnimationDialog::updateAnim(int frame) +{ + _ui.horizontalSlider->setSliderPosition(frame); +} + +void CAnimationDialog::setInPlace(bool state) +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + entity.setInPlace(state); +} + +void CAnimationDialog::setIncPos(bool state) +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + entity.setIncPos(state); +} + +void CAnimationDialog::finish() +{ + if (_ui.loopCheckBox->checkState() == Qt::Checked) play(); + else _ui.playPushButton->setChecked(false); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/animation_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/animation_dialog.h new file mode 100644 index 000000000..e042a2122 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/animation_dialog.h @@ -0,0 +1,91 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef ANIMATION_DIALOG_H +#define ANIMATION_DIALOG_H + +#include +#include "ui_animation_form.h" + +// Qt includes +#include + +// STL includes + +// NeL includes +#include "nel/3d/animation_time.h" + +// Project includes + +namespace NLQT { + +/** +@class CAnimationDialog +@brief Animation model control dialog. +@details The dialogue doesn't affect on the model itself, but only calculates the current time of animations +that can be obtained through a class method getTime(). +The user can influence on the duration of the animation, but he doesn't know the total time for all the animations in the playlist. +Therefore, the class provides a slot that requires a total duration of the animated object animations and set it. +*/ +class CAnimationDialog: public QDockWidget +{ + Q_OBJECT + +public: + /// Constructor, sets the default length of time from 0 to 99 + CAnimationDialog(QWidget *parent = 0); + ~CAnimationDialog(); + + /// Get the current time animations + /// @return Returns the current time animations, which can then be use in class CObjectViewer + NL3D::TAnimationTime getTime (); + +public Q_SLOTS: + /// Find the total time of the playlist and sets its + void changeAnimLength(); + + /// Updates animation status for the selected current object + /// @param name - the name of the selected object + void setCurrentShape(const QString &name); + +private Q_SLOTS: + void start(); + void play(); + void stop(); + void end(); + void changeFrame(int frame); + void changeStartAnim(int start); + void changeEndAnim(int end); + void updateAnim(int frame); + void setInPlace(bool state); + void setIncPos(bool state); + void finish(); + +private: + + static const int _frameRate = 50; + QTimeLine *_timeLine; + Ui::CAnimationDialog _ui; + + friend class CMainWindow; +}; /* CAnimationDialog */ + +} /* namespace NLQT */ + +#endif // ANIMATION_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/animation_form.ui b/code/nel/tools/3d/object_viewer_qt/src/animation_form.ui new file mode 100644 index 000000000..aceb1eaec --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/animation_form.ui @@ -0,0 +1,392 @@ + + + CAnimationDialog + + + + 0 + 0 + 519 + 98 + + + + + 0 + 0 + + + + + 519 + 98 + + + + + 524287 + 524287 + + + + false + + + QDockWidget::AllDockWidgetFeatures + + + Qt::AllDockWidgetAreas + + + Animation + + + + + + + + + 99 + + + 10 + + + Qt::Horizontal + + + false + + + false + + + QSlider::TicksBelow + + + 10 + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 999999 + + + + + + + + + 0 + + + QLayout::SetMaximumSize + + + 0 + + + + + QAbstractSpinBox::UpDownArrows + + + false + + + 999999 + + + + + + + Loop + + + + + + + true + + + In place + + + + + + + true + + + Increment pos + + + + + + + false + + + 999999 + + + 100 + + + + + + + + + + 25 + 25 + + + + + 25 + 25 + + + + + + + + :/images/skip-backward.png:/images/skip-backward.png + + + + 28 + 28 + + + + true + + + + + + + + 25 + 25 + + + + + 25 + 25 + + + + + + + + :/images/seek-backward.png:/images/seek-backward.png + + + + 28 + 28 + + + + true + + + + + + + + 25 + 25 + + + + + 25 + 25 + + + + + + + + :/images/play.png + :/images/pause.png:/images/play.png + + + + 28 + 28 + + + + true + + + true + + + + + + + + 25 + 25 + + + + + 25 + 25 + + + + + + + + :/images/stop.png:/images/stop.png + + + + 28 + 28 + + + + false + + + true + + + + + + + + 25 + 25 + + + + + 25 + 25 + + + + + + + + :/images/seek-forward.png:/images/seek-forward.png + + + + 28 + 28 + + + + true + + + + + + + + 25 + 25 + + + + + 25 + 25 + + + + + + + + :/images/skip-forward.png:/images/skip-forward.png + + + + 28 + 28 + + + + true + + + + + + + + + + + + currentSpinBox + valueChanged(int) + horizontalSlider + setValue(int) + + + 151 + 55 + + + 163 + 77 + + + + + horizontalSlider + valueChanged(int) + currentSpinBox + setValue(int) + + + 224 + 85 + + + 141 + 55 + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.cpp new file mode 100644 index 000000000..474f7a929 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.cpp @@ -0,0 +1,293 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "animation_set_dialog.h" + +// Qt includes +#include + +// NeL includes + +// Project includes +#include "modules.h" +#include "entity.h" + +namespace NLQT { + +CAnimationSetDialog::CAnimationSetDialog(QWidget *parent) + : QDockWidget(parent) +{ + ui.setupUi(this); + + connect(ui.addToolButton, SIGNAL(clicked()), this, SLOT(addAnim())); + connect(ui.removeToolButton, SIGNAL(clicked()), this, SLOT(removeAnim())); + connect(ui.upToolButton, SIGNAL(clicked()), this, SLOT(upAnim())); + connect(ui.downToolButton, SIGNAL(clicked()), this, SLOT(downAnim())); + connect(ui.addAnimPushButton, SIGNAL(clicked()), this, SLOT(loadAnim())); + connect(ui.addSwtPushButton, SIGNAL(clicked()), this, SLOT(loadSwt())); + connect(ui.resetPushButton, SIGNAL(clicked()), this, SLOT(resetAnim())); + + connect(ui.listRadioButton, SIGNAL(clicked(bool)), this, SLOT(setModeAnim())); + connect(ui.mixerRadioButton, SIGNAL(clicked(bool)), this, SLOT(setModeAnim())); + + connect(ui.objectsComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(setCurrentShape(QString))); +} + +CAnimationSetDialog::~CAnimationSetDialog() +{ +} + +void CAnimationSetDialog::setCurrentShape(const QString &name) +{ + if (name.isEmpty()) + return; + + Modules::objView().setCurrentObject(name.toStdString()); + + updateListAnim(); + + Q_EMIT changeCurrentShape(name); + +} + +void CAnimationSetDialog::setModeAnim() +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + + if (ui.mixerRadioButton->isChecked()) + entity.setMode(CEntity::Mode::Mixer); + else + entity.setMode(CEntity::Mode::PlayList); +} + +void CAnimationSetDialog::updateListObject() +{ + ui.objectsComboBox->clear(); + + std::vector listObjects; + Modules::objView().getListObjects(listObjects); + + for (size_t i = 0; i < listObjects.size(); i++) + ui.objectsComboBox->addItem(QString(listObjects[i].c_str())); + + if (listObjects.empty()) + { + ui.addAnimPushButton->setEnabled(false); + ui.addSwtPushButton->setEnabled(false); + ui.resetPushButton->setEnabled(false); + ui.setLengthPushButton->setEnabled(false); + } + else + { + ui.addAnimPushButton->setEnabled(true); + ui.addSwtPushButton->setEnabled(true); + ui.resetPushButton->setEnabled(true); + ui.setLengthPushButton->setEnabled(true); + } +} + +void CAnimationSetDialog::updateListAnim() +{ + ui.animTreeWidget->clear(); + ui.animPlaylistWidget->clear(); + ui.skeletonTreeWidget->clear(); + + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + + std::vector& animationList = entity.getAnimationList(); + std::vector& swtList = entity.getSWTList(); + std::vector& playListAnimation = entity.getPlayListAnimation(); + + // update animation list widget + for(size_t i = 0; i < animationList.size(); ++i) + { + QTreeWidgetItem *item = new QTreeWidgetItem(ui.animTreeWidget); + item->setText(0,QString(animationList[i].c_str())); + } + + // update skeleton weight template list widget + for(size_t i = 0; i < swtList.size(); ++i) + { + QTreeWidgetItem *item = new QTreeWidgetItem(ui.skeletonTreeWidget); + item->setText(0,QString(swtList[i].c_str())); + } + + // update PlayList animation widget + for(size_t i = 0; i < playListAnimation.size(); ++i) + { + QListWidgetItem *item = new QListWidgetItem(ui.animPlaylistWidget); + item->setText(QString(playListAnimation[i].c_str())); + } + + if (animationList.empty()) + { + // lock buttons + ui.addToolButton->setEnabled(false); + ui.removeToolButton->setEnabled(false); + ui.upToolButton->setEnabled(false); + ui.downToolButton->setEnabled(false); + } + else + { + // unlock buttons + ui.addToolButton->setEnabled(true); + ui.removeToolButton->setEnabled(true); + ui.upToolButton->setEnabled(true); + ui.downToolButton->setEnabled(true); + } +} + +void CAnimationSetDialog::loadAnim() +{ + CEntity &entity = Modules::objView().getEntity(Modules::objView().getCurrentObject()); + + QStringList fileNames = QFileDialog::getOpenFileNames(this, + tr("Open NeL anim file"), ".", + tr("NeL anim files (*.anim);;")); + + setCursor(Qt::WaitCursor); + + if (!fileNames.isEmpty()) + { + QStringList list = fileNames; + QStringList::Iterator it = list.begin(); + while(it != list.end()) + { + std::string animName = it->toStdString(); + entity.loadAnimation(animName); + ++it; + } + updateListAnim(); + } + + setCursor(Qt::ArrowCursor); +} + +void CAnimationSetDialog::loadSwt() +{ + CEntity &entity = Modules::objView().getEntity(Modules::objView().getCurrentObject()); + + QStringList fileNames = QFileDialog::getOpenFileNames(this, + tr("Open NeL anim file"), ".", + tr("NeL Skeleton Weight Template files (*.swt);;")); + + setCursor(Qt::WaitCursor); + + if (!fileNames.isEmpty()) + { + QStringList list = fileNames; + QStringList::Iterator it = list.begin(); + while(it != list.end()) + { + std::string swtName = it->toStdString(); + entity.loadSWT(swtName); + ++it; + } + updateListAnim(); + } + + setCursor(Qt::ArrowCursor); +} + +void CAnimationSetDialog::resetAnim() +{ + CEntity &entity = Modules::objView().getEntity(Modules::objView().getCurrentObject()); + entity.reset(); + + updateListAnim(); +} + +void CAnimationSetDialog::addAnim() +{ + CEntity &entity = Modules::objView().getEntity(Modules::objView().getCurrentObject()); + QList list = ui.animTreeWidget->selectedItems(); + + Q_FOREACH(QTreeWidgetItem* item, list) + { + std::string animName = item->text(0).toStdString(); + entity.addAnimToPlayList(animName); + ui.animPlaylistWidget->addItem(item->text(0)); + } +} + +void CAnimationSetDialog::removeAnim() +{ + CEntity &entity = Modules::objView().getEntity(Modules::objView().getCurrentObject()); + QList list = ui.animPlaylistWidget->selectedItems(); + + Q_FOREACH(QListWidgetItem* item, list) + { + int row = ui.animPlaylistWidget->row(item); + QListWidgetItem *removeItem = ui.animPlaylistWidget->takeItem(row); + if (!removeItem) + delete removeItem; + entity.removeAnimToPlayList(row); + } +} + +void CAnimationSetDialog::upAnim() +{ + CEntity &entity = Modules::objView().getEntity(Modules::objView().getCurrentObject()); + QList list = ui.animPlaylistWidget->selectedItems(); + + if (list.empty()) + return; + + int frontRow = ui.animPlaylistWidget->row(list.front()); + int backRow = ui.animPlaylistWidget->row(list.back()); + + if (frontRow == 0) + return; + + QListWidgetItem *item = ui.animPlaylistWidget->takeItem(frontRow - 1); + ui.animPlaylistWidget->insertItem(backRow, item); + + for (int i = frontRow; i <= backRow; ++i) + entity.swapAnimToPlayList(i - 1, i); +} + +void CAnimationSetDialog::downAnim() +{ + CEntity &entity = Modules::objView().getEntity(Modules::objView().getCurrentObject()); + QList list = ui.animPlaylistWidget->selectedItems(); + + if (list.empty()) + return; + + int frontRow = ui.animPlaylistWidget->row(list.front()); + int backRow = ui.animPlaylistWidget->row(list.back()); + + if (backRow == (ui.animPlaylistWidget->count() - 1)) + return; + + QListWidgetItem *item = ui.animPlaylistWidget->takeItem(backRow + 1); + ui.animPlaylistWidget->insertItem(frontRow, item); + + for (int i = backRow; i >= frontRow; --i) + entity.swapAnimToPlayList(i, i + 1); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.h new file mode 100644 index 000000000..5fb85c5ff --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/animation_set_dialog.h @@ -0,0 +1,86 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef ANIMATION_SET_DIALOG_H +#define ANIMATION_SET_DIALOG_H + +#include "ui_animation_set_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NLQT { + /** +@class CAnimationSetDialog +@brief Dialog - Animations control, loading animation, weight for skeleton and playlist composition. +@details Dialog loads animations files and weight for skeleton animation. Files can also be unloaded. +For loading / unloading and animation control dialog uses the functionality of CEntity class. +But the main opportunity for dialogue is to generating animations playlists. +Also, the dialogue has a control element to select the current shape (emit a signal changeCurrentShape()) +and switch of playlist/mixer (this functionality will soon be transferred to another specialized dialogue). +As each shape has its own list of loaded animations and playlist,when you switch your current shape, +the dialogue should be notified through the slot setCurrentShape (). + */ +class CAnimationSetDialog: public QDockWidget +{ + Q_OBJECT + +public: + CAnimationSetDialog(QWidget *parent = 0); + ~CAnimationSetDialog(); + + /// Update the objects list (this function should be moved to another dialogue) + void updateListObject(); + + /// Update the list of loaded animation files + void updateListAnim(); + +Q_SIGNALS: + /// Signal emitted when changing the current animation object. + void changeCurrentShape(const QString &name); + +public Q_SLOTS: + + /// Updates and displays the list of loaded animations and playlist for the selected current object + /// @param name - the name of the selected object + void setCurrentShape(const QString &name); + +private Q_SLOTS: + void setModeAnim(); + void loadAnim(); + void loadSwt(); + void resetAnim(); + void addAnim(); + void removeAnim(); + void upAnim(); + void downAnim(); + +private: + + Ui::CAnimationSetDialog ui; + + friend class CMainWindow; +}; /* CAnimationDialog */ + +} /* namespace NLQT */ + +#endif // ANIMATION_SET_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/animation_set_form.ui b/code/nel/tools/3d/object_viewer_qt/src/animation_set_form.ui new file mode 100644 index 000000000..9d1a03380 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/animation_set_form.ui @@ -0,0 +1,480 @@ + + + CAnimationSetDialog + + + + 0 + 0 + 300 + 562 + + + + + 0 + 0 + + + + + 300 + 390 + + + + + :/images/animset.png:/images/animset.png + + + Animation set manager + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + Edited object: + + + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + Qt::Vertical + + + + + + + Animations: + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + false + + + QAbstractItemView::NoDragDrop + + + QAbstractItemView::ExtendedSelection + + + 1 + + + false + + + false + + + + 1 + + + + + + + + + + + + Skeleton weight template: + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 16777215 + 5646546 + + + + false + + + false + + + + 1 + + + + + + + + + + + + + Animations playlist: + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + false + + + QAbstractItemView::NoDragDrop + + + Qt::IgnoreAction + + + QAbstractItemView::ContiguousSelection + + + + + + + false + + + ... + + + + :/images/list-add.png:/images/list-add.png + + + + 20 + 20 + + + + + + + + false + + + ... + + + + :/images/list-remove.png:/images/list-remove.png + + + + 20 + 20 + + + + + + + + false + + + ... + + + + :/images/go-up.png:/images/go-up.png + + + + 20 + 20 + + + + + + + + false + + + ... + + + + :/images/go-down.png:/images/go-down.png + + + + 20 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Add anim + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Add swt + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Reset + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Set anim length + + + + + + + + + + + Use the list + + + true + + + + + + + true + + + Use the mixer + + + + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/attrib_form.ui b/code/nel/tools/3d/object_viewer_qt/src/attrib_form.ui new file mode 100644 index 000000000..3a3778109 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/attrib_form.ui @@ -0,0 +1,291 @@ + + + CAttribWidget + + + + 0 + 0 + 276 + 273 + + + + GroupBox + + + + + + + + + + Constant + + + + + Scheme + + + + + + + schemeWidget + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Input mutliplier: + + + + + + + Qt::Horizontal + + + + 108 + 20 + + + + + + + + false + + + Clamp + + + + + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 1 + + + + + + + + true + + + + + + + + + + 30 + 16777215 + + + + E + + + + + + + + + Src: + + + + + + + + 0 + 0 + + + + + Date + + + + + Position + + + + + Inverse Mass + + + + + Speed + + + + + Random + + + + + User param + + + + + LOD + + + + + Square LOD + + + + + Clamped LOD + + + + + Camped square LOD + + + + + + + + + + false + + + + 30 + 16777215 + + + + U + + + + + + + false + + + + 30 + 16777215 + + + + P + + + + + + + false + + + + 30 + 16777215 + + + + G + + + + + + + + + + + NLQT::CEditRangeUIntWidget + QWidget +

edit_range_widget.h
+ 1 + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CBasicEditWidget + QWidget +
basic_edit_widget.h
+ 1 +
+ + NLQT::CColorEditWidget + QWidget +
color_edit_widget.h
+ 1 +
+ + NLQT::CEditRangeIntWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/attrib_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/attrib_widget.cpp new file mode 100644 index 000000000..bd8d6c7c0 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/attrib_widget.cpp @@ -0,0 +1,972 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "attrib_widget.h" + +// Qt includes +#include + +// NeL includes +#include +#include +#include +#include +#include +#include + +// Projects includes +#include "value_blender_dialog.h" +#include "value_gradient_dialog.h" +#include "bin_op_dialog.h" +#include "curve_dialog.h" +#include "value_from_emitter_dialog.h" +#include "spinner_dialog.h" +#include "follow_path_dialog.h" + +namespace NLQT { + +CAttribWidget::CAttribWidget(QWidget *parent) + : QGroupBox(parent), + _SrcInputEnabled(true), + _EnableConstantValue(true), + _DisableMemoryScheme(false), + _NbCycleEnabled(true), + _Node(NULL), _SchemeWidget(NULL) +{ + _ui.setupUi(this); + _ui.constRangeUIntWidget->hide(); + _ui.constRangeFloatWidget->hide(); + _ui.constAttribPlaneWidget->hide(); + _ui.constRangeIntWidget->hide(); + _ui.constRGBAWidget->hide(); + _ui.schemeWidget->hide(); + + _ui.inMultiplierWidget->setRange(0.1f, 10.1f); + _ui.inMultiplierWidget->enableLowerBound(0, true);; + _ui.inMultiplierWidget->setWrapper(&_NbCyclesWrapper); + _NbCyclesWrapper.widget = this; +} + +CAttribWidget::~CAttribWidget() +{ +} + +void CAttribWidget::setEnabledConstantValue(bool enableConstantValue) +{ + _EnableConstantValue = enableConstantValue; +} + +void CAttribWidget::init() +{ + connect(_ui.editPushButton, SIGNAL(clicked()), this, SLOT(clickedEdit())); + connect(_ui.clampCheckBox, SIGNAL(toggled(bool)), this, SLOT(setClamp(bool))); + connect(_ui.schemeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changeCurrentScheme(int))); + connect(_ui.srcComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentSrc(int))); + connect(_ui.userParamPushButton, SIGNAL(clicked()), this, SLOT(setUserIndex())); + connect(_ui.comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(changeUseScheme(int))); +} + +void CAttribWidget::updateUi() +{ + if (!_EnableConstantValue) + _ui.comboBox->hide(); + else + _ui.comboBox->show(); + + if (useScheme()) + { + if (_ui.comboBox->currentIndex() == 1) + schemeValueUpdate(); + else + _ui.comboBox->setCurrentIndex(1); + } + else + { + + nlassert(_EnableConstantValue); + if (_ui.comboBox->currentIndex() == 0) + cstValueUpdate(); + else + _ui.comboBox->setCurrentIndex(0); + } +} + +void CAttribWidget::clickedEdit() +{ + QDialog *dialog = editScheme(); + if (dialog != NULL) + { + dialog->setModal(true); + dialog->show(); + dialog->exec(); + delete dialog; + } +} + +void CAttribWidget::setClamp(bool state) +{ + // avoid performance warning + if (state != isSchemeClamped()) + clampScheme(state); +} + +void CAttribWidget::changeCurrentScheme(int index) +{ + if (getCurrentScheme() != index) + setCurrentScheme(uint(index)); + schemeValueUpdate(); +} + +void CAttribWidget::setCurrentSrc(int index) +{ + NL3D::CPSInputType it; + it.InputType = (NL3D::CPSInputType::TInputType) index; + if (it.InputType != getSchemeInput().InputType) + { + if (it.InputType == NL3D::CPSInputType::attrUserParam) + { + it.UserParamNum = 0; + } + setSchemeInput(it); + } + inputValueUpdate(); +} + +void CAttribWidget::setUserIndex() +{ + bool ok; + int i = QInputDialog::getInt(this, tr("Set user param"), tr(""), getSchemeInput().UserParamNum + 1, 1, 4, 1, &ok); + if (ok) + { + NL3D::CPSInputType it = getSchemeInput(); + it.UserParamNum = i - 1; + setSchemeInput(it); + } + inputValueUpdate(); +} + +void CAttribWidget::changeUseScheme(int index) +{ + if (index == 0) + { + if (!useScheme()) + resetCstValue(); // change constant + cstValueUpdate(); // update ui + } + else + { + if (useScheme()) + changeCurrentScheme(getCurrentScheme()); // update ui + else + changeCurrentScheme(0); // change scheme + } +} + +void CAttribWidget::inputValueUpdate(void) +{ + if (useScheme() && getSchemeInput().InputType == NL3D::CPSInputType::attrUserParam) + { + //_ui.userParamPushButton->setText(tr("User param: %1").arg(getSchemeInput().UserParamNum + 1)); + _ui.userParamPushButton->setEnabled(true); + } + else + { + //_ui.userParamPushButton->setText(tr("User param:")); + _ui.userParamPushButton->setEnabled(false); + } +} + +void CAttribWidget::schemeValueUpdate() +{ + if (!useScheme()) return; + + _ui.constRangeUIntWidget->hide(); + _ui.constRangeFloatWidget->hide(); + _ui.constAttribPlaneWidget->hide(); + _ui.constRangeIntWidget->hide(); + _ui.constRGBAWidget->hide(); + + _ui.schemeWidget->show(); + + sint k = getCurrentScheme(); + + if (k == -1) // unknow scheme ... + { + _ui.schemeComboBox->setCurrentIndex(k); + k = 0; + } + + if (k != _ui.schemeComboBox->currentIndex()) + _ui.schemeComboBox->setCurrentIndex(k); + + if (hasSchemeCustomInput() && _SrcInputEnabled) + { + _ui.srcLabel->setEnabled(true); + _ui.srcComboBox->setEnabled(true); + _ui.srcComboBox->setCurrentIndex(int(getSchemeInput().InputType)); + + _ui.clampCheckBox->setEnabled(isClampingSupported()); + _ui.inMultiplierWidget->setEnabled(isClampingSupported()); + _ui.inputLabel->setEnabled(isClampingSupported()); + } + else + { + _ui.srcLabel->setEnabled(false); + _ui.srcComboBox->setEnabled(false); + inputValueUpdate(); + + _ui.clampCheckBox->setEnabled(false); + _ui.inMultiplierWidget->setEnabled(false); + _ui.inputLabel->setEnabled(false); + } + + if (_NbCycleEnabled) + { + _ui.inMultiplierWidget->updateUi(); + _ui.inMultiplierWidget->show(); + _ui.inputLabel->show(); + _ui.inMultiplierWidget->setEnabled(true); + _ui.clampCheckBox->show(); + } + else + { + _ui.inputLabel->hide(); + _ui.inMultiplierWidget->hide(); + _ui.clampCheckBox->hide(); + } + + if (isClampingSupported()) + _ui.clampCheckBox->setChecked(isSchemeClamped()); +} + +void CAttribWidget::enableMemoryScheme(bool enabled) +{ + _DisableMemoryScheme = !enabled; + if (!enabled) + { + _ui.schemeComboBox->removeItem(_ui.schemeComboBox->count() - 1); + _ui.schemeComboBox->removeItem(_ui.schemeComboBox->count() - 1); + } +} + +CAttribFloatWidget::CAttribFloatWidget(QWidget *parent) + : CAttribWidgetT(parent) +{ + _ui.schemeComboBox->addItem(tr("value blender")); + _ui.schemeComboBox->addItem(tr("values gradient")); + _ui.schemeComboBox->addItem(tr("curve")); + _ui.schemeComboBox->addItem(tr("value computed from emitter")); + _ui.schemeComboBox->addItem(tr("binary operator")); +} + +CAttribFloatWidget::~CAttribFloatWidget() +{ +} + +void CAttribFloatWidget::setRange(float minValue, float maxValue) +{ + _MinRange = minValue; + _MaxRange = maxValue; + _ui.constRangeFloatWidget->setRange(_MinRange, _MaxRange); +} + +void CAttribFloatWidget::setWrapper(IPSWrapper *wrapper) +{ + nlassert(wrapper); + _Wrapper = wrapper; + _ui.constRangeFloatWidget->setWrapper(_Wrapper); +} + +QDialog *CAttribFloatWidget::editScheme(void) +{ + NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) + { + CFloatBlenderDialogClient *myInterface = new CFloatBlenderDialogClient(); + myInterface->MinRange = _MinRange; + myInterface->MaxRange = _MaxRange; + myInterface->SchemeFunc = & ((NL3D::CPSValueBlenderSample *) scheme)->_F; + CValueBlenderDialog *vb = new CValueBlenderDialog(myInterface, _Node, true, this); + return vb; + } + if (dynamic_cast(scheme)) + { + CFloatGradientWrapper *wrapper = new CFloatGradientWrapper; + wrapper->MinRange = _MinRange; + wrapper->MaxRange = _MaxRange; + wrapper->Scheme = &(((NL3D::CPSFloatGradient *) (_SchemeWrapper->getScheme()) )->_F); + CGradientDialog *gd = new CGradientDialog(_Node, wrapper, true, true, 2, this); + wrapper->DefaultValue = 0.f; + return gd; + } + if (dynamic_cast(scheme)) + { + CAttribFloatWidget *adf = new CAttribFloatWidget(); + adf->setRange(_MinRange, _MaxRange); + CValueFromEmitterDialogT *vfe = new CValueFromEmitterDialogT( (NL3D::CPSFloatMemory *)(scheme), + adf, + this); + vfe->init(); + adf->setWorkspaceNode(_Node); + adf->updateUi(); + return vfe; + } + if (dynamic_cast(scheme)) + { + CAttribFloatWidget *ad[2] = { NULL, NULL}; + for (uint k = 0; k <2; ++k) + { + ad[k] = new CAttribFloatWidget(); + ad[k]->setRange(_MinRange, _MaxRange); + } + CBinOpDialogT *bod = new CBinOpDialogT( (NL3D::CPSFloatBinOp *)(scheme), + (CAttribWidgetT **) ad, + this); + bod->init(); + for (uint k = 0; k <2; ++k) + { + ad[k]->setWorkspaceNode(_Node); + ad[k]->updateUi(); + } + return bod; + } + if (dynamic_cast(scheme)) + { + CurveEditDialog *curve = new CurveEditDialog(&(dynamic_cast(scheme)->_F), _Node, this); + return curve; + } + return NULL; +} + +void CAttribFloatWidget::setCurrentScheme(uint index) +{ + nlassert(index < 5); + NL3D::CPSAttribMaker *scheme = NULL; + + switch (index) + { + case 0: + scheme = new NL3D::CPSFloatBlender(_MinRange, _MaxRange); + break; + case 1: + { + static const float values[2] = { 0.1f, 1.f }; + scheme = new NL3D::CPSFloatGradient(values, 2, 16, 1.f); + } + break; + case 2: + { + NL3D::CPSFloatCurve *curve = new NL3D::CPSFloatCurve; + curve->_F.setNumSamples(128); + curve->_F.addControlPoint(NL3D::CPSFloatCurveFunctor::CCtrlPoint(0, 0.5f)); + curve->_F.addControlPoint(NL3D::CPSFloatCurveFunctor::CCtrlPoint(1, 0.5f)); + scheme = curve; + } + break; + case 3: + scheme = new NL3D::CPSFloatMemory; + ((NL3D::CPSAttribMakerMemory *) scheme)->setScheme(new NL3D::CPSFloatBlender(_MinRange, _MaxRange)); + break; + case 4 : + scheme = new NL3D::CPSFloatBinOp; + ((NL3D::CPSFloatBinOp *) scheme)->setArg(0, new NL3D::CPSFloatBlender); + ((NL3D::CPSFloatBinOp *) scheme)->setArg(1, new NL3D::CPSFloatBlender); + break; + default: + break; + } + + if (scheme) + { + _SchemeWrapper->setSchemeAndUpdateModifiedFlag(scheme); + } +} + +sint CAttribFloatWidget::getCurrentScheme(void) const +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) + return 0; + if (dynamic_cast(scheme)) + return 1; + if (dynamic_cast(scheme)) + return 2; + if (dynamic_cast(scheme)) + return 3; + if (dynamic_cast(scheme)) + return 4; + + return -1; +} + +void CAttribFloatWidget::cstValueUpdate() +{ + _ui.schemeWidget->hide(); + _ui.inMultiplierWidget->setEnabled(false); + _ui.clampCheckBox->setEnabled(false); + _ui.inputLabel->setEnabled(false); + _ui.constRangeFloatWidget->show(); + _ui.constRangeFloatWidget->updateUi(); +} + +CAttribUIntWidget::CAttribUIntWidget(QWidget *parent) + : CAttribWidgetT(parent) +{ + _ui.schemeComboBox->addItem(tr("value blender")); + _ui.schemeComboBox->addItem(tr("values gradient")); + _ui.schemeComboBox->addItem(tr("value computed from emitter")); + _ui.schemeComboBox->addItem(tr("binary operator")); +} + +CAttribUIntWidget::~CAttribUIntWidget() +{ +} + +void CAttribUIntWidget::setRange(uint32 minValue, uint32 maxValue) +{ + _MinRange = minValue; + _MaxRange = maxValue; + _ui.constRangeUIntWidget->setRange(_MinRange, _MaxRange); +} + +void CAttribUIntWidget::setWrapper(IPSWrapper *wrapper) +{ + nlassert(wrapper); + _Wrapper = wrapper; + _ui.constRangeUIntWidget->setWrapper(_Wrapper); +} + +QDialog *CAttribUIntWidget::editScheme(void) +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) + { + CUIntBlenderDialogClient *myInterface = new CUIntBlenderDialogClient(); + myInterface->MinRange = _MinRange; + myInterface->MaxRange = _MaxRange; + myInterface->SchemeFunc = & ((NL3D::CPSValueBlenderSample *) scheme)->_F; + CValueBlenderDialog *vb = new CValueBlenderDialog(myInterface, _Node, true, this); + return vb; + } + if (dynamic_cast(scheme)) + { + CUIntGradientWrapper *wrapper = new CUIntGradientWrapper; + wrapper->MinRange = _MinRange; + wrapper->MaxRange = _MaxRange; + wrapper->Scheme = &(((NL3D::CPSUIntGradient *) (_SchemeWrapper->getScheme()) )->_F); + CGradientDialog *gd = new CGradientDialog(_Node, wrapper, true, true, 2, this); + wrapper->DefaultValue = 0; + return gd; + + } + if (dynamic_cast(scheme)) + { + CAttribUIntWidget *adu = new CAttribUIntWidget(); + adu->setRange(_MinRange, _MaxRange); + CValueFromEmitterDialogT *vfe = new CValueFromEmitterDialogT( (NL3D::CPSUIntMemory *)(scheme), + adu, + this); + vfe->init(); + adu->setWorkspaceNode(_Node); + adu->updateUi(); + return vfe; + } + if (dynamic_cast(scheme)) + { + CAttribUIntWidget *ad[2] = { NULL, NULL}; + for (uint k = 0; k <2; ++k) + { + ad[k] = new CAttribUIntWidget(); + ad[k]->setRange(_MinRange, _MaxRange); + } + CBinOpDialogT *bod = new CBinOpDialogT( (NL3D::CPSUIntBinOp *)(scheme), + (CAttribWidgetT **) ad, + this); + bod->init(); + for (uint k = 0; k <2; ++k) + { + ad[k]->setWorkspaceNode(_Node); + ad[k]->updateUi(); + } + return bod; + } + return NULL; +} + +void CAttribUIntWidget::setCurrentScheme(uint index) +{ + nlassert(index < 4); + NL3D::CPSAttribMaker *scheme = NULL; + + switch (index) + { + case 0 : + scheme = new NL3D::CPSUIntBlender(_MinRange, _MaxRange); + break; + case 1 : + scheme = new NL3D::CPSUIntGradient; + break; + case 2 : + scheme = new NL3D::CPSUIntMemory; + ((NL3D::CPSAttribMakerMemory *) scheme)->setScheme(new NL3D::CPSUIntBlender(_MinRange, _MaxRange) ); + break; + case 3 : + scheme = new NL3D::CPSUIntBinOp; + ((NL3D::CPSUIntBinOp *) scheme)->setArg(0, new NL3D::CPSUIntBlender); + ((NL3D::CPSUIntBinOp *) scheme)->setArg(1, new NL3D::CPSUIntBlender); + break; + default: + break; + } + if (scheme) + { + _SchemeWrapper->setSchemeAndUpdateModifiedFlag(scheme); + } +} + +sint CAttribUIntWidget::getCurrentScheme(void) const +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) return 0; + if (dynamic_cast(scheme)) return 1; + if (dynamic_cast(scheme)) return 2; + if (dynamic_cast(scheme)) return 3; + return -1; +} + +void CAttribUIntWidget::cstValueUpdate() +{ + _ui.schemeWidget->hide(); + _ui.inMultiplierWidget->setEnabled(false); + _ui.clampCheckBox->setEnabled(false); + _ui.inputLabel->setEnabled(false); + _ui.constRangeUIntWidget->show(); + _ui.constRangeUIntWidget->updateUi(); +} + +CAttribIntWidget::CAttribIntWidget(QWidget *parent) + : CAttribWidgetT(parent) +{ + _ui.schemeComboBox->addItem(tr("value exact blender")); + _ui.schemeComboBox->addItem(tr("values gradient")); + _ui.schemeComboBox->addItem(tr("value computed from emitter")); + _ui.schemeComboBox->addItem(tr("binary operator")); +} + +CAttribIntWidget::~CAttribIntWidget() +{ +} + +void CAttribIntWidget::setRange(sint32 minValue, sint32 maxValue) +{ + _MinRange = minValue; + _MaxRange = maxValue; + _ui.constRangeIntWidget->setRange(_MinRange, _MaxRange); +} + +void CAttribIntWidget::setWrapper(IPSWrapper *wrapper) +{ + nlassert(wrapper); + _Wrapper = wrapper; + _ui.constRangeIntWidget->setWrapper(_Wrapper); +} + +QDialog *CAttribIntWidget::editScheme(void) +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) + { + CIntBlenderDialogClient *myInterface = new CIntBlenderDialogClient(); + myInterface->MinRange = _MinRange; + myInterface->MaxRange = _MaxRange; + myInterface->SchemeFunc = & ((NL3D::CPSValueBlenderSample *) scheme)->_F; + CValueBlenderDialog *vb = new CValueBlenderDialog(myInterface, _Node, true, this); + return vb; + } + if (dynamic_cast(scheme)) + { + CIntGradientWrapper *wrapper = new CIntGradientWrapper; + wrapper->MinRange = _MinRange; + wrapper->MaxRange = _MaxRange; + wrapper->Scheme = &(((NL3D::CPSIntGradient *) (_SchemeWrapper->getScheme()) )->_F); + CGradientDialog *gd = new CGradientDialog(_Node, wrapper, true, true, 2, this); + wrapper->DefaultValue = 0; + return gd; + } + if (dynamic_cast(scheme)) + { + CAttribIntWidget *adi = new CAttribIntWidget(); + adi->setRange(_MinRange, _MaxRange); + CValueFromEmitterDialogT *vfe = new CValueFromEmitterDialogT((NL3D::CPSIntMemory *) _SchemeWrapper->getScheme(), + adi, this); + vfe->init(); + adi->setWorkspaceNode(_Node); + adi->updateUi(); + return vfe; + } + if (dynamic_cast(scheme)) + { + CAttribIntWidget *ad[2] = { NULL, NULL}; + for (uint k = 0; k <2; ++k) + { + ad[k] = new CAttribIntWidget(); + ad[k]->setRange(_MinRange, _MaxRange); + } + CBinOpDialogT *bod = new CBinOpDialogT( (NL3D::CPSIntBinOp *)(scheme), + (CAttribWidgetT **) ad, + this); + bod->init(); + for (uint k = 0; k <2; ++k) + { + ad[k]->setWorkspaceNode(_Node); + ad[k]->updateUi(); + } + return bod; + } + return NULL; +} + +void CAttribIntWidget::setCurrentScheme(uint index) +{ + nlassert(index < 4); + NL3D::CPSAttribMaker *scheme = NULL; + + switch (index) + { + case 0 : + scheme = new NL3D::CPSIntBlender; + break; + case 1 : + scheme = new NL3D::CPSIntGradient; + break; + case 2 : + scheme = new NL3D::CPSIntMemory; + ((NL3D::CPSAttribMakerMemory *) scheme)->setScheme(new NL3D::CPSIntBlender(_MinRange, _MaxRange)); + break; + case 3 : + scheme = new NL3D::CPSIntBinOp; + ((NL3D::CPSIntBinOp *) scheme)->setArg(0, new NL3D::CPSIntBlender); + ((NL3D::CPSIntBinOp *) scheme)->setArg(1, new NL3D::CPSIntBlender); + break; + default: + break; + } + if (scheme) + { + _SchemeWrapper->setSchemeAndUpdateModifiedFlag(scheme); + } +} + +sint CAttribIntWidget::getCurrentScheme(void) const +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) return 0; + if (dynamic_cast(scheme)) return 1; + if (dynamic_cast(scheme)) return 2; + if (dynamic_cast(scheme)) return 3; + return -1; +} + +void CAttribIntWidget::cstValueUpdate() +{ + _ui.schemeWidget->hide(); + _ui.inMultiplierWidget->setEnabled(false); + _ui.clampCheckBox->setEnabled(false); + _ui.inputLabel->setEnabled(false); + _ui.constRangeIntWidget->show(); + //_ui.constRangeIntWidget->updateUi(); +} + +CAttribRGBAWidget::CAttribRGBAWidget(QWidget *parent) + : CAttribWidgetT(parent) +{ + _ui.schemeComboBox->addItem(tr("color sampled blender")); + _ui.schemeComboBox->addItem(tr("color gradient")); + _ui.schemeComboBox->addItem(tr("color exact blender")); + _ui.schemeComboBox->addItem(tr("values computed from emitter")); + _ui.schemeComboBox->addItem(tr("binary operator")); +} + +CAttribRGBAWidget::~CAttribRGBAWidget() +{ +} + +void CAttribRGBAWidget::setWrapper(IPSWrapper *wrapper) +{ + nlassert(wrapper); + _Wrapper = wrapper; + _ui.constRGBAWidget->setWrapper(_Wrapper); +} + +QDialog *CAttribRGBAWidget::editScheme(void) +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) + { + CRGBABlenderDialogClient *myInterface = new CRGBABlenderDialogClient(); + myInterface->SchemeFunc = & ((NL3D::CPSValueBlenderSample *) scheme)->_F; + CValueBlenderDialog *vb = new CValueBlenderDialog(myInterface, _Node, true, this); + return vb; + } + if (dynamic_cast(scheme)) + { + CColorGradientWrapper *wrapper = new CColorGradientWrapper; + wrapper->Scheme = &(((NL3D::CPSColorGradient *) (_SchemeWrapper->getScheme()) )->_F); + CGradientDialog *gd = new CGradientDialog(_Node, wrapper, true, true, 2, this); + wrapper->DefaultValue = NLMISC::CRGBA::White; + return gd; + } + if (dynamic_cast(scheme)) + { + return NULL; + } + if (dynamic_cast(scheme)) + { + CAttribRGBAWidget *ad = new CAttribRGBAWidget(); + CValueFromEmitterDialogT *vfe = new CValueFromEmitterDialogT( (NL3D::CPSColorMemory *)(scheme), + ad, + this); + vfe->init(); + ad->setWorkspaceNode(_Node); + ad->updateUi(); + return vfe; + } + if (dynamic_cast(scheme)) + { + CAttribRGBAWidget *ad[2] = { NULL, NULL}; + for (uint k = 0; k <2; ++k) + { + ad[k] = new CAttribRGBAWidget(); + } + CBinOpDialogT *bod = new CBinOpDialogT( (NL3D::CPSColorBinOp *)(scheme), + (CAttribWidgetT **) ad, + this); + bod->init(); + for (uint k = 0; k <2; ++k) + { + ad[k]->setWorkspaceNode(_Node); + ad[k]->updateUi(); + } + return bod; + } + return NULL; +} + +void CAttribRGBAWidget::setCurrentScheme(uint index) +{ + nlassert(index < 5); + NL3D::CPSAttribMaker *scheme = NULL; + + switch (index) + { + case 0 : + scheme = new NL3D::CPSColorBlender; + break; + case 1 : + scheme = new NL3D::CPSColorGradient(NL3D::CPSColorGradient::_DefaultGradient, 2, 16, 1.f); + break; + case 2 : + scheme = new NL3D::CPSColorBlenderExact; + break; + case 3 : + scheme = new NL3D::CPSColorMemory; + ((NL3D::CPSAttribMakerMemory *) scheme)->setScheme(new NL3D::CPSColorBlender); + break; + case 4 : + scheme = new NL3D::CPSColorBinOp; + ((NL3D::CPSColorBinOp *) scheme)->setArg(0, new NL3D::CPSColorBlender); + ((NL3D::CPSColorBinOp *) scheme)->setArg(1, new NL3D::CPSColorBlender); + break; + default: + break; + } + if (scheme) + { + _SchemeWrapper->setSchemeAndUpdateModifiedFlag(scheme); + } +} + +sint CAttribRGBAWidget::getCurrentScheme(void) const +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) return 0; + if (dynamic_cast(scheme)) return 1; + if (dynamic_cast(scheme)) return 2; + if (dynamic_cast(scheme)) return 3; + if (dynamic_cast(scheme)) return 4; + return -1; +} + +void CAttribRGBAWidget::cstValueUpdate() +{ + _ui.schemeWidget->hide(); + _ui.inMultiplierWidget->setEnabled(false); + _ui.clampCheckBox->setEnabled(false); + _ui.inputLabel->setEnabled(false); + _ui.constRGBAWidget->show(); + _ui.constRGBAWidget->updateUi(); +} + +CAttribPlaneBasisWidget::CAttribPlaneBasisWidget(QWidget *parent) + : CAttribWidgetT(parent) +{ + _ui.schemeComboBox->addItem(tr("basis gradient")); + _ui.schemeComboBox->addItem(tr("follow path")); + _ui.schemeComboBox->addItem(tr("spinner")); + _ui.schemeComboBox->addItem(tr("values computed from emitter")); + _ui.schemeComboBox->addItem(tr("binary operator")); +} + +CAttribPlaneBasisWidget::~CAttribPlaneBasisWidget() +{ +} + +void CAttribPlaneBasisWidget::setWrapper(IPSWrapper *wrapper) +{ + nlassert(wrapper); + _Wrapper = wrapper; + _ui.constAttribPlaneWidget->setWrapper(_Wrapper); +} + +QDialog *CAttribPlaneBasisWidget::editScheme(void) +{ + NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + if (dynamic_cast(scheme)) + { + CPlaneBasisGradientWrapper *wrapper = new CPlaneBasisGradientWrapper; + wrapper->Scheme = &(((NL3D::CPSPlaneBasisGradient *) (_SchemeWrapper->getScheme()) )->_F); + CGradientDialog *gd = new CGradientDialog(_Node, wrapper, true, true, 2, this); + wrapper->DefaultValue = NL3D::CPlaneBasis(NLMISC::CVector::K); + return gd; + } + if (dynamic_cast(scheme)) + { + CFollowPathDialog *dialog = new CFollowPathDialog(dynamic_cast(scheme), _Node, this); + return dialog; + } + if (dynamic_cast(scheme)) + { + CAttribPlaneBasisWidget *ad = new CAttribPlaneBasisWidget(); + CValueFromEmitterDialogT *vfe = new CValueFromEmitterDialogT + ( (NL3D::CPSPlaneBasisMemory *)(scheme), + ad, this); + vfe->init(); + ad->setWorkspaceNode(_Node); + ad->updateUi(); + return vfe; + } + if (dynamic_cast(scheme)) + { + CAttribPlaneBasisWidget *ad[2] = { NULL, NULL}; + for (uint k = 0; k <2; ++k) + { + ad[k] = new CAttribPlaneBasisWidget(); + } + CBinOpDialogT *bod = new CBinOpDialogT( (NL3D::CPSPlaneBasisBinOp *)(scheme), + (CAttribWidgetT **) ad, + this); + bod->init(); + for (uint k = 0; k <2; ++k) + { + ad[k]->setWorkspaceNode(_Node); + ad[k]->updateUi(); + } + return bod; + } + if (dynamic_cast(scheme)) + { + CSpinnerDialog *dialog = new CSpinnerDialog(dynamic_cast(scheme), _Node, this); + return dialog; + } + return NULL; +} + +void CAttribPlaneBasisWidget::setCurrentScheme(uint index) +{ + nlassert(index < 5); + NL3D::CPSAttribMaker *scheme = NULL; + + switch (index) + { + case 0: + scheme = new NL3D::CPSPlaneBasisGradient; + break; + case 1: + scheme = new NL3D::CPSPlaneBasisFollowSpeed; + break; + case 2: + scheme = new NL3D::CPSBasisSpinner; + static_cast(scheme)->_F.setNumSamples(16); + break; + case 3: + scheme = new NL3D::CPSPlaneBasisMemory; + ((NL3D::CPSAttribMakerMemory *) scheme)->setScheme(new NL3D::CPSPlaneBasisFollowSpeed); + if (_Node) + { + _Node->setModified(true); + } + break; + case 4 : + scheme = new NL3D::CPSPlaneBasisBinOp; + ((NL3D::CPSPlaneBasisBinOp *) scheme)->setArg(0, new NL3D::CPSPlaneBasisFollowSpeed); + ((NL3D::CPSPlaneBasisBinOp *) scheme)->setArg(1, new NL3D::CPSPlaneBasisFollowSpeed); + break; + default: + break; + } + + if (scheme) + { + _SchemeWrapper->setSchemeAndUpdateModifiedFlag(scheme); + } +} + +sint CAttribPlaneBasisWidget::getCurrentScheme(void) const +{ + const NL3D::CPSAttribMaker *scheme = _SchemeWrapper->getScheme(); + + if (dynamic_cast(scheme)) return 0; + if (dynamic_cast(scheme)) return 1; + if (dynamic_cast(scheme)) return 2; + if (dynamic_cast(scheme)) return 3; + if (dynamic_cast(scheme)) return 4; + + return -1; +} + +void CAttribPlaneBasisWidget::cstValueUpdate() +{ + _ui.schemeWidget->hide(); + _ui.inMultiplierWidget->setEnabled(false); + _ui.clampCheckBox->setEnabled(false); + _ui.inputLabel->setEnabled(false); + _ui.constAttribPlaneWidget->show(); + _ui.constAttribPlaneWidget->updateUi(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/attrib_widget.h b/code/nel/tools/3d/object_viewer_qt/src/attrib_widget.h new file mode 100644 index 000000000..7349bfbd6 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/attrib_widget.h @@ -0,0 +1,362 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef ATTRIB_WIDGET_H +#define ATTRIB_WIDGET_H + +#include +#include "ui_attrib_form.h" + +// Qt includes +#include + +// STL includes + +// NeL includes +#include +#include +#include +#include + +// Project includes +#include "ps_wrapper.h" +#include "particle_node.h" + +namespace NLQT { +/** +@class CAttribWidget +@brief Base attrib maker edition dialog. +*/ +class CAttribWidget: public QGroupBox +{ + Q_OBJECT + +public: + CAttribWidget(QWidget *parent = 0); + ~CAttribWidget(); + + /// @param enableConstantValue - when false, only a scheme is available + void setEnabledConstantValue(bool enableConstantValue = true); + + /// Force to update dialog content + void updateUi(); + + /// Сonnects all the slots with signals + void init(); + + /// Sets the pointer CWorkspaceNode* in the wrappers. + virtual void setWorkspaceNode(CWorkspaceNode *node) = 0; + + /// Private usage (not private because accessed by a static function) : return the nbCycles parameter of the scheme (e.g the input multiplier). + virtual float getSchemeNbCycles(void) const = 0; + + /// Private usage (not private because accessed by a static function) : set the nbCycles parameter of the scheme (e.g the input multiplier) + virtual void setSchemeNbCycles(float nbCycles) = 0; + + /// Enable the srcInput + void enableSrcInput(bool enable = true) { _SrcInputEnabled = enable; } + + bool isSrcInputEnabled() const { return _SrcInputEnabled; } + + /// Disable the possibility to choose a scheme that has memory. (for example, a scheme for lifetime of a located has no sense + /// because located have already some memory to store it) + void enableMemoryScheme(bool enabled = true); + + /// Tells wether memory schemes are enables + /// @see enableMemoryScheme() + bool isMemorySchemeEnabled() const { return !_DisableMemoryScheme; } + + /// Enable Nb Cycle tuning + void enableNbCycles(bool enabled) { _NbCycleEnabled = enabled; } + bool isNbCycleEnabled() const { return _NbCycleEnabled; } + +private Q_SLOTS: + virtual void clickedEdit(); + virtual void setClamp(bool state); + virtual void changeCurrentScheme(int index); + virtual void setCurrentSrc(int index); + virtual void setUserIndex(); + virtual void changeUseScheme(int index); + +protected: + + /// change the dialog for constant values + virtual void cstValueUpdate() = 0; + + /// enable / disable the 'edit input' button, when input can be edited + void inputValueUpdate(void); + + /// toggle back from scheme to cst value + virtual void resetCstValue(void) = 0; + + /// change the dialog for scheme usage + void schemeValueUpdate(); + + /// return true if a scheme is used + virtual bool useScheme(void) const = 0; + + /// edit the current scheme. And return a window on it + virtual QDialog *editScheme(void) = 0; + + /// set the current scheme + virtual void setCurrentScheme(uint index) = 0; + + /// set the current scheme ptr + virtual void setCurrentSchemePtr(NL3D::CPSAttribMakerBase *) = 0; + + /// get the current scheme, -1 if the scheme is unknow (created outside the editor ?) + virtual sint getCurrentScheme(void) const = 0; + + /// get a pointer on the current scheme base class + virtual NL3D::CPSAttribMakerBase *getCurrentSchemePtr(void) const = 0; + + /// tells wether the scheme supports custom input + virtual bool hasSchemeCustomInput(void) const = 0; + + /// retrieve the scheme input id + virtual NL3D::CPSInputType getSchemeInput(void) const = 0; + + /// set the scheme input id + virtual void setSchemeInput(const NL3D::CPSInputType &input) = 0; + + /// tells wether the scheme input value is clamped or not + virtual bool isSchemeClamped(void) const = 0; + + /// clamp / unclamp the scheme + virtual void clampScheme(bool clamped = true) = 0; + + /// return true if clamping is supported + virtual bool isClampingSupported(void) const = 0; + + /// bool : true is src input are allowed + bool _SrcInputEnabled; + + /// true if constant values are allowed + bool _EnableConstantValue; + + /// this is equal to true when memory schemes are not permitted + bool _DisableMemoryScheme; + + /// true to enable 'nb cycles' control + bool _NbCycleEnabled; + + /// wrapper to tune the number of cycles + struct CNbCyclesWrapper : public IPSWrapperFloat + { + CAttribWidget *widget; + float get(void) const { return widget->getSchemeNbCycles(); } + void set(const float &v) { widget->setSchemeNbCycles(v); } + } _NbCyclesWrapper; + + CWorkspaceNode *_Node; + + QDialog *_SchemeWidget; + + Ui::CAttribWidget _ui; +}; /* class CAttribWidget */ + +/** +@class CAttribWidgetT +@brief A template class that helps to specialize the attrib maker edition dialog with various types. +*/ +template class CAttribWidgetT : public CAttribWidget +{ +public: + CAttribWidgetT(QWidget *parent = 0): CAttribWidget(parent), + _Wrapper(NULL), + _SchemeWrapper(NULL) + { + } + virtual void setWrapper(IPSWrapper *wrapper) = 0; + void setSchemeWrapper(IPSSchemeWrapper *schemeWrapper) { nlassert(schemeWrapper); _SchemeWrapper = schemeWrapper; } + + // Inherited from CAttribWidget + virtual QDialog *editScheme(void) = 0; + virtual void setCurrentScheme(uint index) = 0; + virtual sint getCurrentScheme(void) const = 0; + + virtual void resetCstValue(void) + { + _Wrapper->setAndUpdateModifiedFlag(_Wrapper->get()); // reuse current color + } + + virtual bool hasSchemeCustomInput(void) const { return _SchemeWrapper->getScheme()->hasCustomInput(); } + virtual NL3D::CPSInputType getSchemeInput(void) const { return _SchemeWrapper->getScheme()->getInput(); } + virtual void setSchemeInput(const NL3D::CPSInputType &input) { _SchemeWrapper->getScheme()->setInput(input); } + + virtual void setWorkspaceNode(CWorkspaceNode *node) + { + _Node = node; if (_Wrapper != NULL) _Wrapper->OwnerNode = _Node; if (_SchemeWrapper != NULL) _SchemeWrapper->OwnerNode = _Node; + }; + + virtual float getSchemeNbCycles(void) const { return _SchemeWrapper->getScheme()->getNbCycles(); } + virtual void setSchemeNbCycles(float nbCycles) { _SchemeWrapper->getScheme()->setNbCycles(nbCycles); } + + virtual bool isSchemeClamped(void) const { return _SchemeWrapper->getScheme()->getClamping(); } + virtual void clampScheme(bool clamped = true) { _SchemeWrapper->getScheme()->setClamping(clamped); } + virtual bool isClampingSupported(void) const { return _SchemeWrapper->getScheme()->isClampingSupported(); }; + virtual NL3D::CPSAttribMakerBase *getCurrentSchemePtr(void) const { return _SchemeWrapper->getScheme(); } + virtual void setCurrentSchemePtr(NL3D::CPSAttribMakerBase *s) + { + _SchemeWrapper->setSchemeAndUpdateModifiedFlag(NLMISC::safe_cast *>(s)); + } + virtual void cstValueUpdate() = 0; + +protected: + virtual bool useScheme(void) const + { + nlassert(_SchemeWrapper); + return(_SchemeWrapper->getScheme() != NULL); + } +public: + /// Wrapper to set/get a constant float + IPSWrapper *_Wrapper; + /// Wrapper to set/get a scheme + IPSSchemeWrapper *_SchemeWrapper; +}; + +/** +@class CAttribFloatWidget +@brief An attribute editor specialized for float values +*/ +class CAttribFloatWidget: public CAttribWidgetT +{ + Q_OBJECT + +public: + CAttribFloatWidget(QWidget *parent = 0); + ~CAttribFloatWidget(); + + void setRange(float minValue = 0, float maxValue = 10); + void setWrapper(IPSWrapper *wrapper); + + // inherited from CAttribWidget + virtual QDialog *editScheme(void); + virtual void setCurrentScheme(uint index); + virtual sint getCurrentScheme(void) const; + virtual void cstValueUpdate(); + +private: + + float _MinRange, _MaxRange; +}; /* class CAttribFloatWidget */ + +/** +@class CAttribUIntWidget +@brief An attribute editor specialized for unsigned int values +*/ +class CAttribUIntWidget: public CAttribWidgetT +{ + Q_OBJECT + +public: + CAttribUIntWidget(QWidget *parent = 0); + ~CAttribUIntWidget(); + + void setRange(uint32 minValue = 0, uint32 maxValue = 10); + void setWrapper(IPSWrapper *wrapper); + + // inherited from CAttribWidget + virtual QDialog *editScheme(void); + virtual void setCurrentScheme(uint index); + virtual sint getCurrentScheme(void) const; + virtual void cstValueUpdate(); + +private: + + uint32 _MinRange, _MaxRange; +}; /* class CAttribUIntWidget */ + +/** +@class CAttribIntWidget +@brief An attribute editor specialized for signed int values +*/ +class CAttribIntWidget: public CAttribWidgetT +{ + Q_OBJECT + +public: + CAttribIntWidget(QWidget *parent = 0); + ~CAttribIntWidget(); + + void setRange(sint32 minValue = 0, sint32 maxValue = 10); + void setWrapper(IPSWrapper *wrapper); + + // inherited from CAttribWidget + virtual QDialog *editScheme(void); + virtual void setCurrentScheme(uint index); + virtual sint getCurrentScheme(void) const; + virtual void cstValueUpdate(); + +private: + + sint32 _MinRange, _MaxRange; +}; /* class CAttribIntWidget */ + +/** +@class CAttribRGBAWidget +@brief An attribute editor specialized for RGB values +*/ +class CAttribRGBAWidget: public CAttribWidgetT +{ + Q_OBJECT + +public: + CAttribRGBAWidget(QWidget *parent = 0); + ~CAttribRGBAWidget(); + + void setWrapper(IPSWrapper *wrapper); + + // inherited from CAttribWidget + virtual QDialog *editScheme(void); + virtual void setCurrentScheme(uint index); + virtual sint getCurrentScheme(void) const; + virtual void cstValueUpdate(); + +private: + +}; /* class CAttribRGBAWidget */ + +/** +@class CAttribPlaneBasisWidget +@brief An attribute editor specialized for plane basis values +*/ +class CAttribPlaneBasisWidget: public CAttribWidgetT +{ + Q_OBJECT + +public: + CAttribPlaneBasisWidget(QWidget *parent = 0); + ~CAttribPlaneBasisWidget(); + + void setWrapper(IPSWrapper *wrapper); + + // inherited from CAttribWidget + virtual QDialog *editScheme(void); + virtual void setCurrentScheme(uint index); + virtual sint getCurrentScheme(void) const; + virtual void cstValueUpdate(); + +private: + +}; /* class CAttribPlaneBasisWidget */ + +} /* namespace NLQT */ + +#endif // ATTRIB_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.cpp new file mode 100644 index 000000000..7c6fe651b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.cpp @@ -0,0 +1,88 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "auto_lod_dialog.h" + +// STL includes + +// Qt includes + +// NeL includes + +// Project includes + +namespace NLQT { + +CAutoLODDialog::CAutoLODDialog(CWorkspaceNode *ownerNode, NL3D::CParticleSystem *ps, QWidget *parent) + : QDialog(parent), _Node(ownerNode), _PS(ps) +{ + _ui.setupUi(this); + + setFixedHeight(sizeHint().height()); + + _DistRatioWrapper.PS = _PS; + _MaxDistLODBiasWrapper.PS = _PS; + _DistRatioWrapper.OwnerNode = _Node; + _MaxDistLODBiasWrapper.OwnerNode = _Node; + + // Edit the distance at which LOD starts + _ui.startPercentDistWidget->setRange(0.f, 0.99f); + _ui.startPercentDistWidget->enableUpperBound(1.f, true); + _ui.startPercentDistWidget->enableLowerBound(0.f, false); + _ui.startPercentDistWidget->setWrapper(&_DistRatioWrapper); + _ui.startPercentDistWidget->updateUi(); + + // For non-shared systems only : Set the LOD bias at the max distance, so that some particles are still displayed + _ui.maxDistBiasWidget->setRange(0.f, 1.0f); + _ui.maxDistBiasWidget->enableUpperBound(1.f, false); + _ui.maxDistBiasWidget->enableLowerBound(0.f, false); + _ui.maxDistBiasWidget->setWrapper(&_MaxDistLODBiasWrapper); + _ui.maxDistBiasWidget->updateUi(); + + if (_PS->isSharingEnabled()) + { + _ui.maxDistBiasWidget->setEnabled(false); + _ui.maxDistBiasLabel->setEnabled(false); + } + else + _ui.skipParticlesCheckBox->setEnabled(false); + + _ui.degrdExponentSpinBox->setValue(int(_PS->getAutoLODDegradationExponent()) - 1); + _ui.skipParticlesCheckBox->setChecked(_PS->getAutoLODMode()); + + connect(_ui.degrdExponentSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setDegradationExponent(int))); + connect(_ui.skipParticlesCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSkipParticles(bool))); +} + +CAutoLODDialog::~CAutoLODDialog() +{ +} + +void CAutoLODDialog::setDegradationExponent(int value) +{ + _PS->setupAutoLOD(_PS->getAutoLODStartDistPercent(),value + 1); +} + +void CAutoLODDialog::setSkipParticles(bool state) +{ + _PS->setAutoLODMode(state); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.h new file mode 100644 index 000000000..bc699f2be --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/auto_lod_dialog.h @@ -0,0 +1,73 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef AUTO_LOD_DIALOG_H +#define AUTO_LOD_DIALOG_H + +#include "ui_auto_lod_form.h" + +// STL includes + +// Qt includes + +// NeL includes +#include "nel/3d/particle_system.h" + +// Project includes +#include "ps_wrapper.h" + +namespace NLQT { + +class CAutoLODDialog: public QDialog +{ + Q_OBJECT + +public: + CAutoLODDialog(CWorkspaceNode *ownerNode, NL3D::CParticleSystem *ps, QWidget *parent = 0); + ~CAutoLODDialog(); + +private Q_SLOTS: + void setDegradationExponent(int value); + void setSkipParticles(bool state); + +private: + CWorkspaceNode *_Node; + + NL3D::CParticleSystem *_PS; + + struct CDistRatioWrapper : IPSWrapperFloat + { + NL3D::CParticleSystem *PS; + virtual float get() const { return PS->getAutoLODStartDistPercent(); } + virtual void set(const float &v) { PS->setupAutoLOD(v, PS->getAutoLODDegradationExponent()); } + } _DistRatioWrapper; + + struct CMaxDistLODBiasWrapper : IPSWrapperFloat + { + NL3D::CParticleSystem *PS; + virtual float get() const { return PS->getMaxDistLODBias(); } + virtual void set(const float &v) { PS->setMaxDistLODBias(v); } + } _MaxDistLODBiasWrapper; + + Ui::CAutoLODDialog _ui; +}; /* class CAutoLODDialog */ + +} /* namespace NLQT */ + +#endif // AUTO_LOD_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/auto_lod_form.ui b/code/nel/tools/3d/object_viewer_qt/src/auto_lod_form.ui new file mode 100644 index 000000000..8fd459721 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/auto_lod_form.ui @@ -0,0 +1,151 @@ + + + CAutoLODDialog + + + + 0 + 0 + 543 + 137 + + + + Auto LOD + + + true + + + + + + Start percent DIST: + + + + + + + + + + Max dist BIAS: + + + + + + + + + + Degradation exponent: + + + + + + + 1 + + + 4 + + + + + + + Qt::Horizontal + + + + 331 + 21 + + + + + + + + Skip particles + + + + + + + Qt::Horizontal + + + + 139 + 22 + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+
+ + + + buttonBox + accepted() + CAutoLODDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CAutoLODDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/basic_edit_form.ui b/code/nel/tools/3d/object_viewer_qt/src/basic_edit_form.ui new file mode 100644 index 000000000..ce6cbac40 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/basic_edit_form.ui @@ -0,0 +1,214 @@ + + + CBasicEditWidget + + + + 0 + 0 + 283 + 99 + + + + Form + + + + + + Psi + + + + + + + 359 + + + Qt::Horizontal + + + + + + + 359 + + + + + + + + 0 + 0 + + + + + 75 + 75 + + + + + + + + Theta + + + + + + + 360 + + + Qt::Horizontal + + + + + + + 359 + + + + + + + Phi + + + + + + + 360 + + + Qt::Horizontal + + + + + + + 359 + + + + + + + + NLQT::CGraphicsInfoWidget + QWidget +
graphics_info_widget.h
+ 1 +
+
+ + + + psiHorizontalSlider + valueChanged(int) + psiSpinBox + setValue(int) + + + 126 + 22 + + + 173 + 22 + + + + + thetaHorizontalSlider + valueChanged(int) + thetaSpinBox + setValue(int) + + + 135 + 41 + + + 192 + 41 + + + + + phiHorizontalSlider + valueChanged(int) + phiSpinBox + setValue(int) + + + 134 + 65 + + + 170 + 65 + + + + + phiSpinBox + valueChanged(int) + phiHorizontalSlider + setValue(int) + + + 184 + 77 + + + 138 + 79 + + + + + thetaSpinBox + valueChanged(int) + thetaHorizontalSlider + setValue(int) + + + 179 + 50 + + + 154 + 50 + + + + + psiSpinBox + valueChanged(int) + psiHorizontalSlider + setValue(int) + + + 171 + 9 + + + 155 + 18 + + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.cpp new file mode 100644 index 000000000..fc0914c84 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.cpp @@ -0,0 +1,192 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "basic_edit_widget.h" + +// Qt includes + +// NeL includes +#include "nel/misc/matrix.h" +#include "nel/misc/vector.h" + +namespace NLQT { + + +// build an euler matrix +NLMISC::CMatrix BuildEulerMatrix(float psi, float theta, float phi) +{ + float ca = cosf(psi), sa = sinf(psi) + , cb = cosf(theta), sb = sinf(theta) + , cc = cosf(phi), sc = sinf(phi); + NLMISC::CMatrix m; + m.identity(); + m.setRot(NLMISC::CVector(ca * cb * cc - sa * sc, -cc * sa - ca * cb *sc, ca * sb) + ,NLMISC::CVector(cb * cc * sa + ca * sc, ca * cc - cb * sa * sc, sa *sb) + ,NLMISC::CVector(-cc * sb, sb * sc, cb) + ); + return m; +} + +// get back the euler angles from a matrix +NLMISC::CVector GetEulerAngles(const NLMISC::CMatrix &mat) +{ + float m[3][3]; + // we got cos theta = m33 + NLMISC::CVector v[3]; + mat.getRot(v[0], v[1], v[2]); + for (uint l = 0; l < 3; ++l) + { + m[0][l] = v[l].x; m[1][l] = v[l].y; m[2][l] = v[l].z; + } + + // there are eight triplet that may satisfy the equation + // we compute them all, and test them against the matrix + + float b0, b1, a0, a1, a2, a3, c0, c1, c2, c3; + b0 = acosf(m[2][2]); + b1 = (float) NLMISC::Pi - b0; + float sb0 = sinf(b0), sb1 = sinf(b1); + if (fabsf(sb0) > 10E-6) + { + a0 = m[2][0] / sb0; + c0 = m[1][2] / sb0; + } + else + { + a0 = c0 = 1.f; + } + if (fabs(sb1) > 10E-6) + { + a1 = m[2][0] / sb1; + c1 = m[1][2] / sb1; + } + else + { + a1 = c1 = 1.f; + } + + + a2 = (float) NLMISC::Pi - a0; + a3 = (float) NLMISC::Pi - a1; + + + c2 = (float) NLMISC::Pi - c0; + c3 = (float) NLMISC::Pi - c1; + + NLMISC::CVector sol[] = + { + NLMISC::CVector(b0, a0, c0) + ,NLMISC::CVector(b0, a2, c0) + ,NLMISC::CVector(b0, a0, c2) + ,NLMISC::CVector(b0, a2, c2) + ,NLMISC::CVector(b1, a1, c1) + ,NLMISC::CVector(b1, a3, c1) + ,NLMISC::CVector(b1, a1, c3) + ,NLMISC::CVector(b1, a3, c3) + }; + + // now we take the triplet that fit best the 6 other equations + + float bestGap = 0.f; + uint bestIndex; + + for (uint k = 0; k < 8; ++k) + { + float ca = cosf(sol[k].x), sa = sinf(sol[k].x) + , cb = cosf(sol[k].y), sb = sinf(sol[k].y) + , cc = cosf(sol[k].z), sc = sinf(sol[k].z); + + float gap = fabsf(m[0][0] - ca * cb * cc + sa * sc); + gap += fabsf(m[1][0] + cc * sa + ca * cb *sc); + gap += fabsf(m[0][1] - cb * cc * sa - ca * sc); + gap += fabsf(m[0][1] - cb * cc * sa - ca * sc); + gap += fabsf(m[1][1] - ca * cc + cb * sa * sc); + gap += fabsf(m[2][1] - sb *ca); + gap += fabsf(m[0][2] + cc * sb); + + if (k == 0 || gap < bestGap) + { + bestGap = gap; + bestIndex = k; + + } + } + return sol[bestIndex]; +} + +CBasicEditWidget::CBasicEditWidget(QWidget *parent) + : QWidget(parent), _Wrapper(NULL) +{ + _ui.setupUi(this); + + connect(_ui.psiSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateGraphics())); + connect(_ui.thetaSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateGraphics())); + connect(_ui.phiSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateGraphics())); +} + +CBasicEditWidget::~CBasicEditWidget() +{ +} + +void CBasicEditWidget::setWrapper(IPSWrapper *wrapper) +{ + _Wrapper = wrapper; +} + +void CBasicEditWidget::updateUi() +{ + if (_Wrapper == NULL) return; + NL3D::CPlaneBasis pb = _Wrapper->get(); + NLMISC::CMatrix mat; + mat.setRot(pb.X, pb.Y, pb.X ^ pb.Y); + NLMISC::CVector angles = GetEulerAngles(mat); + + _ui.psiSpinBox->blockSignals(true); + _ui.thetaSpinBox->blockSignals(true); + _ui.phiSpinBox->blockSignals(true); + + _ui.psiSpinBox->setValue(int(360.f * angles.x / (2.f * (float) NLMISC::Pi))); + _ui.thetaSpinBox->setValue(int(360.f * angles.y / (2.f * (float) NLMISC::Pi))); + _ui.phiSpinBox->setValue(int(360.f * angles.z / (2.f * (float) NLMISC::Pi))); + + _ui.psiSpinBox->blockSignals(false); + _ui.thetaSpinBox->blockSignals(false); + _ui.phiSpinBox->blockSignals(false); + + repaint(); +} + +void CBasicEditWidget::updateGraphics() +{ + if (_Wrapper == NULL) return; + + NLMISC::CVector angles(2.f * (float) NLMISC::Pi * _ui.psiSpinBox->value() / 360.f + , 2.f * (float) NLMISC::Pi * _ui.thetaSpinBox->value() / 360.f + , 2.f * (float) NLMISC::Pi * _ui.phiSpinBox->value() / 360.f + ); + NLMISC::CMatrix mat = BuildEulerMatrix(angles.x, angles.y, angles.z); + NL3D::CPlaneBasis pb; + pb.X = mat.getI(); + pb.Y = mat.getJ(); + _Wrapper->setAndUpdateModifiedFlag(pb); + repaint(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.h b/code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.h new file mode 100644 index 000000000..0c9987e05 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/basic_edit_widget.h @@ -0,0 +1,61 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef BASIC_EDIT_WIDGET_H +#define BASIC_EDIT_WIDGET_H + +#include +#include "ui_basic_edit_form.h" + +// STL includes + +// NeL includes +#include "nel/3d/ps_plane_basis.h" +#include "ps_wrapper.h" + +// Project includes + +namespace NLQT { + +class CBasicEditWidget: public QWidget +{ + Q_OBJECT + +public: + CBasicEditWidget(QWidget *parent = 0); + ~CBasicEditWidget(); + + void setWrapper(IPSWrapper *wrapper); + void updateUi(); + +private Q_SLOTS: + void updateGraphics(); + +private: + + // wrapper to the datas + IPSWrapper *_Wrapper ; + + Ui::CBasicEditWidget _ui; + +}; /* class CBasicEditWidget */ + +} /* namespace NLQT */ + +#endif // BASIC_EDIT_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.cpp new file mode 100644 index 000000000..0093f6aa1 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.cpp @@ -0,0 +1,63 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "bin_op_dialog.h" + +namespace NLQT { + +CBinOpDialog::CBinOpDialog(QWidget *widget1, QWidget *widget2, QWidget *parent) + : QDialog(parent) +{ + resize(350, 330); + _gridLayout = new QGridLayout(this); + _gridLayout->addWidget(widget1, 0, 0, 1, 2); + + _comboBox = new QComboBox(this); + _gridLayout->addWidget(_comboBox, 1, 0, 1, 1); + + _horizontalSpacer = new QSpacerItem(267, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + _gridLayout->addItem(_horizontalSpacer, 1, 1, 1, 1); + + _gridLayout->addWidget(widget2, 2, 0, 1, 2); + + setWindowTitle(tr("Bin operator")); + _comboBox->clear(); + _comboBox->insertItems(0, QStringList() + << tr("SelectArg1") + << tr("SelectArg2") + << tr("Modulate") + << tr("Add") + << tr("Subtract")); + + qobject_cast(widget1)->setTitle(tr("Arg1")); + qobject_cast(widget2)->setTitle(tr("Arg2")); + connect(_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setNewOp(int))); +} + +CBinOpDialog::~CBinOpDialog() +{ +} + +void CBinOpDialog::setNewOp(int index) +{ + newOp(uint32(index)); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.h new file mode 100644 index 000000000..67de55f6a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/bin_op_dialog.h @@ -0,0 +1,152 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef BIN_OP_DIALOG_H +#define BIN_OP_DIALOG_H + + +#include + +// Qt includes +#include +#include +#include +#include +#include +#include + +// NeL includes +#include "nel/3d/ps_attrib_maker_bin_op.h" + +// Project includes +#include "ps_wrapper.h" +#include "attrib_widget.h" + +namespace NLQT { + +class CBinOpDialog : public QDialog +{ + Q_OBJECT + +public: + CBinOpDialog(QWidget *widget1, QWidget *widget2, QWidget *parent = 0); + ~CBinOpDialog(); + + virtual void init() = 0; + + /// called when a new operator has been selected + virtual void newOp(uint32 op) = 0 ; + +private Q_SLOTS: + void setNewOp(int index); + +protected: + + QGridLayout *_gridLayout; + QWidget *_widget1; + QComboBox *_comboBox; + QSpacerItem *_horizontalSpacer; + QWidget *_widget2; +}; + + +/** +@class CBinOpDialogT +@brief Construct a dialog that allow to edit a binary operator that produce argument of a particle system +*/ +template class CBinOpDialogT : public CBinOpDialog +{ +public: + /// ctruct the given dialog from the given scheme that owns memory + CBinOpDialogT(NL3D::CPSAttribMakerBinOp *editedScheme, CAttribWidgetT **attrbDlg, QWidget *parent = 0) + : CBinOpDialog(attrbDlg[0], attrbDlg[1], parent), _EditedScheme(editedScheme) + { + for (uint k = 0 ; k < 2 ; ++k) + { + nlassert(attrbDlg); + _AttrbDlg[k] = attrbDlg[k]; + _SchemeWrapper[k].S = _EditedScheme ; + _SchemeWrapper[k].Index = k ; + } + } + + void init() + { + uint k ; + for (k = 0 ; k < 2 ; ++k) + { + _AttrbDlg[k]->setEnabledConstantValue(false) ; + _AttrbDlg[k]->setWrapper(&_DummyWrapper) ; + _AttrbDlg[k]->setSchemeWrapper(&_SchemeWrapper[k]) ; + _AttrbDlg[k]->init(); + } + for (k = 0 ; k < (uint) NL3D::CPSBinOp::last ; ++k) + { + if (_EditedScheme->supportOp( (NL3D::CPSBinOp::BinOp) k)) + { + if ((uint) _EditedScheme->getOp() == k) + _comboBox->setCurrentIndex(k); + } + } + } + + ~CBinOpDialogT() + { + for (uint k = 0 ; k < 2 ; ++k) + { + delete _AttrbDlg[k] ; + } + } + +protected: + + NL3D::CPSAttribMakerBinOp *_EditedScheme ; + + /// the dialogs that allow us to edit the schemes + CAttribWidgetT *_AttrbDlg[2] ; + + /// a wrapper to edit the scheme (which himself owns a scheme !!) + struct CSchemeWrapper : public IPSSchemeWrapper + { + NL3D::CPSAttribMakerBinOp *S ; + uint Index ; + virtual NL3D::CPSAttribMaker *getScheme(void) const { return S->getArg(Index) ; } + virtual void setScheme(NL3D::CPSAttribMaker *s) { S->setArg(Index, s) ; } ; + } _SchemeWrapper[2] ; + + /// a dummy wrapper for constant value. This shouldn't be called , however + struct CDummyWrapper : public IPSWrapper + { + T get(void) const { nlassert(false) ; return T() ; } + void set(const T &) { nlassert(false) ; } + } _DummyWrapper ; + + + void newOp(uint32 op) + { + nlassert(_EditedScheme) ; + if (_EditedScheme->getOp() != (NL3D::CPSBinOp::BinOp) op) + _EditedScheme->setOp((NL3D::CPSBinOp::BinOp) op); + } + +} ; + +} /* namespace NLQT */ + +#endif // BIN_OP_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/callback.cpp b/code/nel/tools/3d/object_viewer_qt/src/callback.cpp new file mode 100644 index 000000000..7cf73a296 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/callback.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 by authors + * + * This file is part of NEL QT. + * NEL QT is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * NEL QT is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NEL QT; see the file COPYING. If not, see + * . + */ + +#include "stdpch.h" +#include "callback.h" + +namespace NLQT { + +} /* namespace NLQT */ + +/* end of file */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/callback.h b/code/nel/tools/3d/object_viewer_qt/src/callback.h new file mode 100644 index 000000000..eba33d564 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/callback.h @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2010 by authors + * + * This file is part of NEL QT. + * NEL QT is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * NEL QT is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NEL QT; see the file COPYING. If not, see + * . + */ + +#ifndef NLQT_CALLBACK_H +#define NLQT_CALLBACK_H +#include + +// STL includes + +// NeL includes +#include + +// Project includes + +namespace NLQT { + +#define NLQT_CALLBACK_TEMPLATE \ +/** \ + * \brief NLQT_CALLBACK_ARGS_CLASS \ + * \date 2009-03-03 18:09GMT \ + * \author Jan Boon (Kaetemi) \ + * Awesome callback template \ + */ \ +template \ +class NLQT_CALLBACK_ARGS_CLASS \ +{ \ + /* Very simple reference counting callback base */ \ + class CCallbackBase \ + { \ + public: \ + CCallbackBase() : m_RefCount(0) \ + { \ + \ + } \ + \ + virtual ~CCallbackBase() \ + { \ + nlassert(!m_RefCount); \ + } \ + \ + void refAdd() \ + { \ + ++m_RefCount; \ + } \ + \ + void refRemove() \ + { \ + --m_RefCount; \ + if (!m_RefCount) \ + delete this; \ + } \ + \ + virtual TReturn callback(NLQT_CALLBACK_ARGS_DECL) = 0; \ + \ + virtual bool equals(const CCallbackBase *callbackBase) = 0; \ + \ + /* disable copy */ \ + CCallbackBase(const CCallbackBase &); \ + CCallbackBase &operator=(const CCallbackBase &); \ + \ + private: \ + uint m_RefCount; \ + }; \ + \ + typedef TReturn TCallbackFunction(NLQT_CALLBACK_ARGS_DECL); \ + class CCallbackFunction : public CCallbackBase \ + { \ + public: \ + CCallbackFunction(TCallbackFunction *callbackFunction) : m_CallbackFunction(callbackFunction) \ + { \ + nlassert(m_CallbackFunction); \ + } \ + \ + virtual ~CCallbackFunction() \ + { \ + m_CallbackFunction = NULL; \ + } \ + \ + virtual TReturn callback(NLQT_CALLBACK_ARGS_DECL) \ + { \ + return m_CallbackFunction(NLQT_CALLBACK_ARGS_IMPL); \ + } \ + \ + virtual bool equals(const CCallbackBase *callbackBase) \ + { \ + const CCallbackFunction *callbackFunction = \ + dynamic_cast(callbackBase); \ + if (!callbackFunction) return false; \ + return m_CallbackFunction == callbackFunction->m_CallbackFunction; \ + } \ + \ + private: \ + TCallbackFunction *m_CallbackFunction; \ + }; \ + \ + template \ + class CCallbackMethod : public CCallbackBase \ + { \ + typedef TReturn (TClass::*TCallbackMethod)(NLQT_CALLBACK_ARGS_DECL); \ + public: \ + CCallbackMethod(TClass *callbackObject, TCallbackMethod callbackMethod) : m_CallbackObject(callbackObject), m_CallbackMethod(callbackMethod) \ + { \ + nlassert(m_CallbackObject); \ + nlassert(m_CallbackMethod); \ + } \ + \ + virtual ~CCallbackMethod() \ + { \ + m_CallbackObject = NULL; \ + m_CallbackMethod = NULL; \ + } \ + \ + virtual TReturn callback(NLQT_CALLBACK_ARGS_DECL) \ + { \ + return (m_CallbackObject->*m_CallbackMethod)(NLQT_CALLBACK_ARGS_IMPL); \ + } \ + \ + virtual bool equals(const CCallbackBase *callbackBase) \ + { \ + const CCallbackMethod *callbackMethod = \ + dynamic_cast(callbackBase); \ + if (!callbackMethod) return false; \ + return m_CallbackObject == callbackMethod->m_CallbackObject \ + && m_CallbackMethod == callbackMethod->m_CallbackMethod; \ + } \ + \ + private: \ + TClass *m_CallbackObject; \ + TCallbackMethod m_CallbackMethod; \ + }; \ + \ +public: \ + CCallback() : m_CallbackBase(NULL) \ + { \ + \ + } \ + \ + CCallback(TCallbackFunction *callbackFunction) : m_CallbackBase(new CCallbackFunction(callbackFunction)) \ + { \ + nlassert(m_CallbackBase); \ + m_CallbackBase->refAdd(); \ + } \ + \ + template \ + CCallback(TClass *callbackObject, TReturn (TClass::*callbackMethod)(NLQT_CALLBACK_ARGS_DECL)) : m_CallbackBase(new CCallbackMethod(callbackObject, callbackMethod)) \ + { \ + nlassert(m_CallbackBase); \ + m_CallbackBase->refAdd(); \ + } \ + \ + CCallback(const CCallback &callback) \ + { \ + m_CallbackBase = callback.m_CallbackBase; \ + if (m_CallbackBase) \ + m_CallbackBase->refAdd(); \ + } \ + \ + CCallback &operator=(const CCallback &callback) \ + { \ + if (m_CallbackBase != callback.m_CallbackBase) \ + { \ + if (m_CallbackBase) \ + m_CallbackBase->refRemove(); \ + m_CallbackBase = callback.m_CallbackBase; \ + if (m_CallbackBase) \ + m_CallbackBase->refAdd(); \ + } \ + return *this; \ + } \ + \ + ~CCallback() \ + { \ + if (m_CallbackBase) \ + { \ + m_CallbackBase->refRemove(); \ + m_CallbackBase = NULL; \ + } \ + } \ + \ + TReturn callback(NLQT_CALLBACK_ARGS_DECL) \ + { \ + nlassert(m_CallbackBase); \ + return m_CallbackBase->callback(NLQT_CALLBACK_ARGS_IMPL); \ + } \ + \ + TReturn operator()(NLQT_CALLBACK_ARGS_DECL) \ + { \ + nlassert(m_CallbackBase); \ + return m_CallbackBase->callback(NLQT_CALLBACK_ARGS_IMPL); \ + } \ + \ + bool valid() const \ + { \ + return m_CallbackBase != NULL; \ + } \ + \ + operator bool() const \ + { \ + return m_CallbackBase != NULL; \ + } \ + \ + bool operator==(const CCallback &callback) \ + { \ + return m_CallbackBase->equals(callback.m_CallbackBase); \ + } \ + \ +private: \ + CCallbackBase *m_CallbackBase; \ + \ +}; /* class CCallback */ \ + +template +class CCallback; + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME +#define NLQT_CALLBACK_ARGS_DECL +#define NLQT_CALLBACK_ARGS_IMPL +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME , typename TArgsA +#define NLQT_CALLBACK_ARGS_DECL TArgsA argsA +#define NLQT_CALLBACK_ARGS_IMPL argsA +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME , typename TArgsA, typename TArgsB +#define NLQT_CALLBACK_ARGS_DECL TArgsA argsA, TArgsB argsB +#define NLQT_CALLBACK_ARGS_IMPL argsA, argsB +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME , typename TArgsA, typename TArgsB, typename TArgsC +#define NLQT_CALLBACK_ARGS_DECL TArgsA argsA, TArgsB argsB, TArgsC argsC +#define NLQT_CALLBACK_ARGS_IMPL argsA, argsB, argsC +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME , typename TArgsA, typename TArgsB, typename TArgsC, typename TArgsD +#define NLQT_CALLBACK_ARGS_DECL TArgsA argsA, TArgsB argsB, TArgsC argsC, TArgsD argsD +#define NLQT_CALLBACK_ARGS_IMPL argsA, argsB, argsC, argsD +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME , typename TArgsA, typename TArgsB, typename TArgsC, typename TArgsD, typename TArgsE +#define NLQT_CALLBACK_ARGS_DECL TArgsA argsA, TArgsB argsB, TArgsC argsC, TArgsD argsD, TArgsE argsE +#define NLQT_CALLBACK_ARGS_IMPL argsA, argsB, argsC, argsD, argsE +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME , typename TArgsA, typename TArgsB, typename TArgsC, typename TArgsD, typename TArgsE, typename TArgsF +#define NLQT_CALLBACK_ARGS_DECL TArgsA argsA, TArgsB argsB, TArgsC argsC, TArgsD argsD, TArgsE argsE, TArgsF argsF +#define NLQT_CALLBACK_ARGS_IMPL argsA, argsB, argsC, argsD, argsE, argsF +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL + +#define NLQT_CALLBACK_ARGS_CLASS CCallback +#define NLQT_CALLBACK_ARGS_TYPENAME , typename TArgsA, typename TArgsB, typename TArgsC, typename TArgsD, typename TArgsE, typename TArgsF, typename TArgsG +#define NLQT_CALLBACK_ARGS_DECL TArgsA argsA, TArgsB argsB, TArgsC argsC, TArgsD argsD, TArgsE argsE, TArgsF argsF, TArgsG argsG +#define NLQT_CALLBACK_ARGS_IMPL argsA, argsB, argsC, argsD, argsE, argsF, argsG +NLQT_CALLBACK_TEMPLATE +#undef NLQT_CALLBACK_ARGS_CLASS +#undef NLQT_CALLBACK_ARGS_TYPENAME +#undef NLQT_CALLBACK_ARGS_DECL +#undef NLQT_CALLBACK_ARGS_IMPL +#undef NLQT_CALLBACK_ARGS_CLASSNAME + +#undef NLQT_CALLBACK_TEMPLATE + +typedef CCallback CEmptyCallback; + +} /* namespace NLQT */ + +#endif /* #ifndef NLQT_CALLBACK_H */ + +/* end of file */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/color_edit_form.ui b/code/nel/tools/3d/object_viewer_qt/src/color_edit_form.ui new file mode 100644 index 000000000..14ed81b98 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/color_edit_form.ui @@ -0,0 +1,264 @@ + + + CColorEditWidget + + + + 0 + 0 + 255 + 116 + + + + Form + + + + + + + 0 + 0 + + + + 255 + + + Qt::Horizontal + + + + + + + QAbstractSpinBox::UpDownArrows + + + 255 + + + + + + + Browse + + + + + + + 255 + + + Qt::Horizontal + + + + + + + 255 + + + + + + + + 0 + 0 + + + + + 75 + 75 + + + + + + + + 255 + + + Qt::Horizontal + + + + + + + 255 + + + + + + + 255 + + + 255 + + + Qt::Horizontal + + + + + + + 255 + + + 255 + + + + + + + + NLQT::CGraphicsInfoWidget + QWidget +
graphics_info_widget.h
+ 1 +
+
+ + + + rHorizontalSlider + valueChanged(int) + rSpinBox + setValue(int) + + + 159 + 75 + + + 231 + 75 + + + + + gHorizontalSlider + valueChanged(int) + gSpinBox + setValue(int) + + + 166 + 115 + + + 214 + 116 + + + + + bHorizontalSlider + valueChanged(int) + bSpinBox + setValue(int) + + + 160 + 160 + + + 210 + 164 + + + + + aHorizontalSlider + valueChanged(int) + aSpinBox + setValue(int) + + + 158 + 201 + + + 210 + 201 + + + + + rSpinBox + valueChanged(int) + rHorizontalSlider + setValue(int) + + + 217 + 81 + + + 167 + 81 + + + + + gSpinBox + valueChanged(int) + gHorizontalSlider + setValue(int) + + + 224 + 130 + + + 184 + 130 + + + + + bSpinBox + valueChanged(int) + bHorizontalSlider + setValue(int) + + + 219 + 154 + + + 171 + 153 + + + + + aSpinBox + valueChanged(int) + aHorizontalSlider + setValue(int) + + + 228 + 198 + + + 187 + 199 + + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.cpp new file mode 100644 index 000000000..3ee8d2f6b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.cpp @@ -0,0 +1,167 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "color_edit_widget.h" + +// Qt includes +#include +#include + +// Nel includes +#include + +namespace NLQT { + +CColorEditWidget::CColorEditWidget(QWidget *parent) + : QWidget(parent), _Wrapper(NULL), _emit(true) +{ + _ui.setupUi(this); + + connect(_ui.rSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setRed(int))); + connect(_ui.gSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setGreen(int))); + connect(_ui.bSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setBlue(int))); + connect(_ui.aSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setAlpha(int))); + connect(_ui.browsePushButton, SIGNAL(clicked()), this, SLOT(browseColor())); + + setColor(QColor(255, 255, 255, 255)); +} + +CColorEditWidget::~CColorEditWidget() +{ +} + +void CColorEditWidget::setWrapper(IPSWrapperRGBA *wrapper) +{ + _Wrapper = wrapper; +} + +void CColorEditWidget::setColor(const NLMISC::CRGBA &color, bool emit) +{ + _emit = false; + _ui.rSpinBox->setValue(color.R); + _ui.gSpinBox->setValue(color.G); + _ui.bSpinBox->setValue(color.B); + _ui.aSpinBox->setValue(color.A); + _emit = true; + if (emit) + Q_EMIT colorChanged(color); +} + +void CColorEditWidget::setColor(const QColor &color, bool emit) +{ + setColor(NLMISC::CRGBA(color.red(), color.green(), color.blue(), color.alpha()), emit); +} + +void CColorEditWidget::updateUi() +{ + if (_Wrapper == NULL) return; + _ui.rSpinBox->setValue(_Wrapper->get().R); + _ui.gSpinBox->setValue(_Wrapper->get().G); + _ui.bSpinBox->setValue(_Wrapper->get().B); + _ui.aSpinBox->setValue(_Wrapper->get().A); + _ui.graphicsWidget->setColor(QColor(_ui.rSpinBox->value(), _ui.gSpinBox->value(), _ui.bSpinBox->value(), _ui.aSpinBox->value())); +} + +void CColorEditWidget::setRed(int r) +{ + _ui.graphicsWidget->setColor(QColor(_ui.rSpinBox->value(), _ui.gSpinBox->value(), _ui.bSpinBox->value(), _ui.aSpinBox->value())); + + if (_emit) + Q_EMIT colorChanged(NLMISC::CRGBA(r, _ui.gSpinBox->value(), _ui.bSpinBox->value(), _ui.aSpinBox->value())); + + if (_Wrapper == NULL) + return; + + NLMISC::CRGBA color = _Wrapper->get(); + + if (r == color.R) + return; + + color.R = r; + _Wrapper->setAndUpdateModifiedFlag(color); + +} + +void CColorEditWidget::setGreen(int g) +{ + _ui.graphicsWidget->setColor(QColor(_ui.rSpinBox->value(), _ui.gSpinBox->value(), _ui.bSpinBox->value(), _ui.aSpinBox->value())); + + if (_emit) + Q_EMIT colorChanged(NLMISC::CRGBA(_ui.rSpinBox->value(), g, _ui.bSpinBox->value(), _ui.aSpinBox->value())); + + if (_Wrapper == NULL) return; + + NLMISC::CRGBA color = _Wrapper->get(); + + if (g == color.G) + return; + + color.G = g; + _Wrapper->setAndUpdateModifiedFlag(color); +} + +void CColorEditWidget::setBlue(int b) +{ + _ui.graphicsWidget->setColor(QColor(_ui.rSpinBox->value(), _ui.gSpinBox->value(), _ui.bSpinBox->value(), _ui.aSpinBox->value())); + + if (_emit) + Q_EMIT colorChanged(NLMISC::CRGBA(_ui.rSpinBox->value(), _ui.gSpinBox->value(), b, _ui.aSpinBox->value())); + + if (_Wrapper == NULL) return; + + NLMISC::CRGBA color = _Wrapper->get(); + + if (b == color.B) + return; + + color.B = b; + _Wrapper->setAndUpdateModifiedFlag(color); +} + +void CColorEditWidget::setAlpha(int a) +{ + _ui.graphicsWidget->setColor(QColor(_ui.rSpinBox->value(), _ui.gSpinBox->value(), _ui.bSpinBox->value(), a)); + + if (_emit) + Q_EMIT colorChanged(NLMISC::CRGBA(_ui.rSpinBox->value(), _ui.gSpinBox->value(), _ui.bSpinBox->value(), a)); + + if (_Wrapper == NULL) return; + + NLMISC::CRGBA color = _Wrapper->get(); + + if (a == color.A) + return; + + color.A = a; + _Wrapper->setAndUpdateModifiedFlag(color); +} + +void CColorEditWidget::browseColor() +{ + QColor color = QColorDialog::getColor(QColor(_ui.rSpinBox->value(), + _ui.gSpinBox->value(), + _ui.bSpinBox->value(), + _ui.aSpinBox->value())); + if (!color.isValid()) return; + + setColor(color); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.h b/code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.h new file mode 100644 index 000000000..8cde0a2d3 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/color_edit_widget.h @@ -0,0 +1,101 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef COLOR_EDIT_WIDGET_H +#define COLOR_EDIT_WIDGET_H + +#include "ui_color_edit_form.h" + +// Qt includes + +// Project includes +#include "ps_wrapper.h" + +namespace NLQT { + +/** +@class CColorEditWidget +@brief The widget provides a 4 horizontal slider or color dialog, to set the color. +@details Using this widget you can set the color(RGBA) using the four sliders or through the color selection dialog. +Widget at the same time displays the color in the shaded rectangle at the side of the sliders. +Use this widget, have two ways: Qt Q_SIGNAL/SLOT or wrapper. + +1. Using the Qt Q_SIGNALS/SLOT can be set current color by class methods setColor() and +changes in current color(QSliders or QColorDialog) emits the signal colorChanged(). + +2. Using wrapper, create wpapper struct, example: +@code +struct CAmbientColorWrapper : public IPSWrapperRGBA +{ + NL3D::UScene *S; + void set(const NLMISC::CRGBA &col) { S->setSunAmbient(col); } + NLMISC::CRGBA get() const { return S->getSunAmbient(); } +} _AmbientColorWrapper; +@endcode +to set the current values, need call class methods updateUi(); +*/ +class CColorEditWidget: public QWidget +{ + Q_OBJECT + +public: + /// Constructor, sets the default color (255, 255, 255, 255) + CColorEditWidget(QWidget *parent = 0); + ~CColorEditWidget(); + + /// Sets the current color. + /// @param color - NeL NLMISC::CRGBA color + /// @param emit - will emit colorChanged() if the new value is different from the old one and param emit = true + void setColor(const NLMISC::CRGBA &color, bool emit = true); + + /// Sets the current color. + /// @param color - Qt QColor color + /// @param emit - will emit colorChanged() if the new value is different from the old one and param emit = true + void setColor(const QColor &color, bool emit = true); + + /// Set a wrapper to get/set the datas. + void setWrapper(IPSWrapperRGBA *wrapper); + + /// Update the content of the widget using the wrapper. + void updateUi(); + +Q_SIGNALS: + void colorChanged(NLMISC::CRGBA color); + +private Q_SLOTS: + void setRed(int r); + void setGreen(int g); + void setBlue(int b); + void setAlpha(int a); + void browseColor(); + +private: + + // wrapper to the datas + IPSWrapperRGBA *_Wrapper; + + bool _emit; + + Ui::CColorEditWidget _ui; + +}; /* class CColorEditWidget */ + +} /* namespace NLQT */ + +#endif // COLOR_EDIT_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/configuration.cpp b/code/nel/tools/3d/object_viewer_qt/src/configuration.cpp new file mode 100644 index 000000000..154f0244f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/configuration.cpp @@ -0,0 +1,222 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "configuration.h" + +// NeL includes +#include +#include +#include +#include + +#include "modules.h" + +using namespace std; +using namespace NLMISC; + +namespace NLQT { + +CConfiguration::CConfiguration() +{ + +} + +CConfiguration::~CConfiguration() +{ + +} + +void CConfiguration::init() +{ + nldebug("CConfiguration::init"); + + // verify data + nlassert(!ConfigCallbacks.size()); + + // load config + try + { + ConfigFile.load(NLQT_CONFIG_FILE); + } + catch(...) + { + nlerror("Error opening files object_viewer.cfg or object_viewer_default.cfg. Check this files"); + } + // setup config file callback + Modules::config().setCallback("SearchPaths", CConfigCallback(this, &CConfiguration::cfcbSearchPaths)); + +} + +void CConfiguration::release() +{ + nldebug("CConfiguration::release"); + + Modules::config().dropCallback("SearchPaths"); + + // save and release the config file + if (ConfigFile.exists("SaveConfig") && ConfigFile.getVarPtr("SaveConfig")->asBool()) + { + ConfigFile.save(); + } + ConfigFile.clear(); + + // release the search paths etc + CPath::releaseInstance(); + + // verify data + nlassert(!ConfigCallbacks.size()); +} + +void CConfiguration::updateUtilities() +{ + //H_AUTO2 + CConfigFile::checkConfigFiles(); +} + +void CConfiguration::configSearchPaths() +{ + cfcbSearchPaths(Modules::config().getConfigFile().getVar("SearchPaths")); +} + +void CConfiguration::configRemapExtensions() +{ + CConfigFile::CVar *var; + var = ConfigFile.getVarPtr("RemapExtensions"); + uint varsize = var->size(); + for (uint i = 0; i < varsize; i += 2) + CPath::remapExtension(var->asString(i), var->asString(i + 1), true); +} + +void CConfiguration::setAndCallback(const std::string &varName, CConfigCallback configCallback) +{ + ConfigCallbacks[varName] = configCallback; + ConfigFile.setCallback(varName, cbConfigCallback); + configCallback(*ConfigFile.getVarPtr(varName)); +} + +void CConfiguration::setCallback(const std::string &varName, CConfigCallback configCallback) +{ + ConfigCallbacks[varName] = configCallback; + ConfigFile.setCallback(varName, cbConfigCallback); +} + +void CConfiguration::dropCallback(const std::string &varName) +{ + ConfigFile.setCallback(varName, NULL); + ConfigCallbacks.erase(varName); +} + +float CConfiguration::getValue(const string &varName, float defaultValue) +{ + if (ConfigFile.exists(varName)) return ConfigFile.getVar(varName).asFloat(); + CConfigFile::CVar varToCopy; + varToCopy.forceAsDouble((double)defaultValue); + ConfigFile.insertVar(varName, varToCopy); + return defaultValue; +} + +double CConfiguration::getValue(const string &varName, double defaultValue) +{ + if (ConfigFile.exists(varName)) return ConfigFile.getVar(varName).asDouble(); + CConfigFile::CVar varToCopy; + varToCopy.forceAsDouble(defaultValue); + ConfigFile.insertVar(varName, varToCopy); + return defaultValue; +} + +int CConfiguration::getValue(const string &varName, int defaultValue) +{ + if (ConfigFile.exists(varName)) return ConfigFile.getVar(varName).asInt(); + CConfigFile::CVar varToCopy; + varToCopy.forceAsInt(defaultValue); + ConfigFile.insertVar(varName, varToCopy); + return defaultValue; +} + +string CConfiguration::getValue(const string &varName, const string &defaultValue) +{ + if (ConfigFile.exists(varName)) return ConfigFile.getVar(varName).asString(); + CConfigFile::CVar varToCopy; + varToCopy.forceAsString(defaultValue); + ConfigFile.insertVar(varName, varToCopy); + return defaultValue; +} + +ucstring CConfiguration::getValue(const string &varName, const ucstring &defaultValue) +{ + if (ConfigFile.exists(varName)) return ucstring::makeFromUtf8(ConfigFile.getVar(varName).asString()); + CConfigFile::CVar varToCopy; + varToCopy.forceAsString(defaultValue.toUtf8()); + ConfigFile.insertVar(varName, varToCopy); + return defaultValue; +} + +bool CConfiguration::getValue(const string &varName, bool defaultValue) +{ + if (ConfigFile.exists(varName)) return ConfigFile.getVar(varName).asBool(); + CConfigFile::CVar varToCopy; + varToCopy.forceAsInt(defaultValue ? 1 : 0); + ConfigFile.insertVar(varName, varToCopy); + return defaultValue; +} + +CRGBA CConfiguration::getValue(const string &varName, const CRGBA &defaultValue) +{ + if (ConfigFile.exists(varName)) + { + return getValue(ConfigFile.getVar(varName), defaultValue); + } + else + { + // create a new value only if one doesn't exist + CConfigFile::CVar varToCopy; + varToCopy.forceAsInt(defaultValue.R); + varToCopy.setAsInt(defaultValue.G, 1); + varToCopy.setAsInt(defaultValue.B, 2); + varToCopy.setAsInt(defaultValue.A, 3); + ConfigFile.insertVar(varName, varToCopy); + } + return defaultValue; +} + +CRGBA CConfiguration::getValue(const CConfigFile::CVar &var, const CRGBA &defaultValue) +{ + if (var.size() >= 3) + { + if (var.size() > 4) nlwarning("RGBA value in config value '%s' is too long, ignoring unused values"); + return CRGBA((uint8)var.asInt(0), (uint8)var.asInt(1), (uint8)var.asInt(2), var.size() >= 4 ? (uint8)var.asInt(3) : 255); + } + nlwarning("Invalid RGBA value in config value '%s', reverting to default { %i, %i, %i, %i }", var.Name.c_str(), (sint)defaultValue.R, (sint)defaultValue.G, (sint)defaultValue.B, (sint)defaultValue.A); + return defaultValue; +} + +void CConfiguration::cbConfigCallback(NLMISC::CConfigFile::CVar &var) +{ + Modules::config().ConfigCallbacks[var.Name](var); +} + +void CConfiguration::cfcbSearchPaths(NLMISC::CConfigFile::CVar &var) +{ + uint varsize = var.size(); + for (uint i = 0; i < varsize; ++i) + CPath::addSearchPath(var.asString(i), true, false); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/configuration.h b/code/nel/tools/3d/object_viewer_qt/src/configuration.h new file mode 100644 index 000000000..9e1765b9f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/configuration.h @@ -0,0 +1,93 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +#include + +// STL includes +#include + +// NeL includes +#include +#include +#include + +// Project includes +#include "callback.h" + +#define NLQT_CONFIG_FILE "object_viewer.cfg" + +namespace NLQT { + +typedef CCallback CConfigCallback; + +/** +@class CConfiguration +@date 2010-02-05 15:44GMT +@author Jan Boon (Kaetemi) +@brief Read / write settings from the configuration file +@details Load the configuration file, and then read/write settings. +As well as automatic reading of the search paths and their sets. +*/ +class CConfiguration +{ +public: + CConfiguration(); + virtual ~CConfiguration(); + + void init(); + void release(); + + void updateUtilities(); + void configSearchPaths(); + void configRemapExtensions(); + + void setAndCallback(const std::string &varName, CConfigCallback configCallback); + void setCallback(const std::string &varName, CConfigCallback configCallback); + void dropCallback(const std::string &varName); + + float getValue(const std::string &varName, float defaultValue); + double getValue(const std::string &varName, double defaultValue); + int getValue(const std::string &varName, int defaultValue); + std::string getValue(const std::string &varName, const std::string &defaultValue); + ucstring getValue(const std::string &varName, const ucstring &defaultValue); + bool getValue(const std::string &varName, bool defaultValue); + NLMISC::CRGBA getValue(const std::string &varName, const NLMISC::CRGBA &defaultValue); + NLMISC::CRGBA getValue(const NLMISC::CConfigFile::CVar &var, const NLMISC::CRGBA &defaultValue); + + inline NLMISC::CConfigFile &getConfigFile() { return ConfigFile; } + +private: + static void cbConfigCallback(NLMISC::CConfigFile::CVar &var); + + void cfcbSearchPaths(NLMISC::CConfigFile::CVar &var); + + CConfiguration(const CConfiguration &); + CConfiguration &operator=(const CConfiguration &); + + NLMISC::CConfigFile ConfigFile; + std::map ConfigCallbacks; + +};/* class CConfiguration */ + +} /* namespace NLQT */ + +#endif // CONFIGURATION_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_form.ui b/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_form.ui new file mode 100644 index 000000000..0a6ab1cde --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_form.ui @@ -0,0 +1,458 @@ + + + CConstraintMeshWidget + + + + 0 + 0 + 238 + 460 + + + + Form + + + + + + Force stage modulation + + + + + + Stage 0 + + + + + + + Stage 1 + + + + + + + Stage 2 + + + + + + + Stage 3 + + + + + + + + + + Force vertex color lighting + + + + + + + Qt::Horizontal + + + + + + + Texture anim type: + + + + + + + 1 + + + + None + + + + + Global + + + + + + + + Reinit when new element is created + + + + + + + Stage: + + + + + + + 3 + + + + + + + QTabWidget::South + + + 1 + + + true + + + + Translation + + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + V Start: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + U Speed: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + V Speed: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + U Accel: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + V Accel: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + U Start: + + + + + + + + + + Scale + + + + + + + + U Start: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + V Start: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + U Speed: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + V Speed: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + U Accel: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + V Accel: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + + + + + + + + Rotation + + + + + + Speed: + + + + + + + 3 + + + -999999.000000000000000 + + + 9999999.000000000000000 + + + 1.000000000000000 + + + + + + + Accel: + + + + + + + 3 + + + -999999.000000000000000 + + + 999999.000000000000000 + + + 1.000000000000000 + + + + + + + Qt::Vertical + + + + 20 + 144 + + + + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.cpp new file mode 100644 index 000000000..bd98f3329 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.cpp @@ -0,0 +1,220 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "constraint_mesh_widget.h" + +// NeL includes +#include "nel/3d/ps_mesh.h" + +namespace NLQT { + +CConstraintMeshWidget::CConstraintMeshWidget(QWidget *parent ) + : QWidget(parent) +{ + _ui.setupUi(this); + + connect(_ui.stageCheckBox_0, SIGNAL(clicked(bool)), this, SLOT(setForceStage0(bool))); + connect(_ui.stageCheckBox_1, SIGNAL(clicked(bool)), this, SLOT(setForceStage1(bool))); + connect(_ui.stageCheckBox_2, SIGNAL(clicked(bool)), this, SLOT(setForceStage2(bool))); + connect(_ui.stageCheckBox_3, SIGNAL(clicked(bool)), this, SLOT(setForceStage3(bool))); + connect(_ui.vertexColorLightingCheckBox, SIGNAL(clicked(bool)), this, SLOT(setForceVertexColorLighting(bool))); + + connect(_ui.texAnimTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setTexAnimType(int))); + connect(_ui.reinitCheckBox, SIGNAL(toggled(bool)), this, SLOT(setReinitWhenNewElementIsCreated(bool))); + connect(_ui.stageSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setStage(int))); + + connectGlobalTexAnim(); +} + +CConstraintMeshWidget::~CConstraintMeshWidget() +{ +} + +void CConstraintMeshWidget::connectGlobalTexAnim() +{ + connect(_ui.transUStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.transVStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.transUSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.transVSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.transUAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.transVAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + + connect(_ui.scaleUStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.scaleVStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.scaleUSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.scaleVSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.scaleUAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.scaleVAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + + connect(_ui.rotSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + connect(_ui.rotAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); +} + +void CConstraintMeshWidget::disconnectGlobalTexAnim() +{ + disconnect(_ui.transUStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.transVStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.transUSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.transVSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.transUAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.transVAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + + disconnect(_ui.scaleUStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.scaleVStartDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.scaleUSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.scaleVSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.scaleUAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.scaleVAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + + disconnect(_ui.rotSpeedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); + disconnect(_ui.rotAccelDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setGlobalTexAnimValue())); +} + +void CConstraintMeshWidget::setCurrentConstraintMesh(CWorkspaceNode *ownerNode, NL3D::CPSConstraintMesh *cm) +{ + _Node = ownerNode; + _CM = cm; + + _ui.stageCheckBox_0->setChecked(_CM->isStageModulationForced(0)); + _ui.stageCheckBox_1->setChecked(_CM->isStageModulationForced(1)); + _ui.stageCheckBox_2->setChecked(_CM->isStageModulationForced(2)); + _ui.stageCheckBox_3->setChecked(_CM->isStageModulationForced(3)); + _ui.vertexColorLightingCheckBox->setChecked(_CM->isVertexColorLightingForced()); + + _ui.texAnimTypeComboBox->setCurrentIndex(_CM->getTexAnimType()); + if (_CM->getTexAnimType() == NL3D::CPSConstraintMesh::GlobalAnim) + { + _ui.reinitCheckBox->setChecked(_CM->isGlobalAnimTimeResetOnNewElementForced()); + updateGlobalTexAnim(0); + } +} + +void CConstraintMeshWidget::setForceStage0(bool state) +{ + _CM->forceStageModulationByColor(0, state); +} + +void CConstraintMeshWidget::setForceStage1(bool state) +{ + _CM->forceStageModulationByColor(1, state); +} + +void CConstraintMeshWidget::setForceStage2(bool state) +{ + _CM->forceStageModulationByColor(2, state); +} + +void CConstraintMeshWidget::setForceStage3(bool state) +{ + _CM->forceStageModulationByColor(3, state); +} + +void CConstraintMeshWidget::setForceVertexColorLighting(bool state) +{ + _CM->forceVertexColorLighting(state); +} + +void CConstraintMeshWidget::setTexAnimType(int index) +{ + switch(index) + { + case 0: // no anim + _CM->setTexAnimType(NL3D::CPSConstraintMesh::NoAnim); + _ui.stageSpinBox->hide(); + _ui.stageLabel->hide(); + _ui.tabWidget->hide(); + _ui.reinitCheckBox->hide(); + break; + case 1: // global anim + _CM->setTexAnimType(NL3D::CPSConstraintMesh::GlobalAnim); + _ui.stageSpinBox->show(); + _ui.stageLabel->show(); + _ui.tabWidget->show(); + _ui.reinitCheckBox->show(); + _ui.stageSpinBox->setValue(0); + _ui.reinitCheckBox->setChecked(_CM->isGlobalAnimTimeResetOnNewElementForced()); + break; + default: + nlstop; + break; + } +} + +void CConstraintMeshWidget::setReinitWhenNewElementIsCreated(bool state) +{ + _CM->forceGlobalAnimTimeResetOnNewElement(state); +} + +void CConstraintMeshWidget::setStage(int value) +{ + updateGlobalTexAnim(value); +} + +void CConstraintMeshWidget::updateGlobalTexAnim(int value) +{ + disconnectGlobalTexAnim(); + + const NL3D::CPSConstraintMesh::CGlobalTexAnim >a = _CM->getGlobalTexAnim(value); + + _ui.transUStartDoubleSpinBox->setValue(gta.TransOffset.x); + _ui.transVStartDoubleSpinBox->setValue(gta.TransOffset.y); + _ui.transUSpeedDoubleSpinBox->setValue(gta.TransSpeed.x); + _ui.transVSpeedDoubleSpinBox->setValue(gta.TransSpeed.y); + _ui.transUAccelDoubleSpinBox->setValue(gta.TransAccel.x); + _ui.transVAccelDoubleSpinBox->setValue(gta.TransAccel.y); + + _ui.scaleUStartDoubleSpinBox->setValue(gta.ScaleStart.x); + _ui.scaleVStartDoubleSpinBox->setValue(gta.ScaleStart.y); + _ui.scaleUSpeedDoubleSpinBox->setValue(gta.ScaleSpeed.x); + _ui.scaleVSpeedDoubleSpinBox->setValue(gta.ScaleSpeed.y); + _ui.scaleUAccelDoubleSpinBox->setValue(gta.ScaleAccel.x); + _ui.scaleVAccelDoubleSpinBox->setValue(gta.ScaleAccel.y); + + _ui.rotSpeedDoubleSpinBox->setValue(gta.WRotSpeed); + _ui.rotAccelDoubleSpinBox->setValue(gta.WRotAccel); + + connectGlobalTexAnim(); +} + +void CConstraintMeshWidget::setGlobalTexAnimValue() +{ + NL3D::CPSConstraintMesh::CGlobalTexAnim gta; + + gta.TransOffset.x = _ui.transUStartDoubleSpinBox->value(); + gta.TransOffset.y = _ui.transVStartDoubleSpinBox->value(); + gta.TransSpeed.x = _ui.transUSpeedDoubleSpinBox->value(); + gta.TransSpeed.y = _ui.transVSpeedDoubleSpinBox->value(); + gta.TransAccel.x = _ui.transUAccelDoubleSpinBox->value(); + gta.TransAccel.y = _ui.transVAccelDoubleSpinBox->value(); + + gta.ScaleStart.x = _ui.scaleUStartDoubleSpinBox->value(); + gta.ScaleStart.y = _ui.scaleVStartDoubleSpinBox->value(); + gta.ScaleSpeed.x = _ui.scaleUSpeedDoubleSpinBox->value(); + gta.ScaleSpeed.y = _ui.scaleVSpeedDoubleSpinBox->value(); + gta.ScaleAccel.x = _ui.scaleUAccelDoubleSpinBox->value(); + gta.ScaleAccel.y = _ui.scaleVAccelDoubleSpinBox->value(); + + gta.WRotSpeed = _ui.rotSpeedDoubleSpinBox->value(); + gta.WRotAccel = _ui.rotAccelDoubleSpinBox->value(); + + _CM->setGlobalTexAnim(_ui.stageSpinBox->value(), gta); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.h b/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.h new file mode 100644 index 000000000..0a7ff91d4 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/constraint_mesh_widget.h @@ -0,0 +1,80 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef CONSTRAINT_MESH_WIDGET_H +#define CONSTRAINT_MESH_WIDGET_H + +#include "ui_constraint_mesh_form.h" + +// STL includes + +// Qt includes + +// NeL includes + +// Project includes +#include "particle_node.h" + +namespace NL3D { + class CPSConstraintMesh; +} + +namespace NLQT { + +/** +@class CConstraintMeshWidget +@brief Widget for setup mesh that have very simple geometry. +*/ +class CConstraintMeshWidget: public QWidget +{ + Q_OBJECT + +public: + CConstraintMeshWidget(QWidget *parent = 0); + ~CConstraintMeshWidget(); + + /// Set the constraint mesh to edit. + void setCurrentConstraintMesh(CWorkspaceNode *ownerNode, NL3D::CPSConstraintMesh *cm); + +private Q_SLOTS: + void setForceStage0(bool state); + void setForceStage1(bool state); + void setForceStage2(bool state); + void setForceStage3(bool state); + void setForceVertexColorLighting(bool state); + void setTexAnimType(int index); + void setReinitWhenNewElementIsCreated(bool state); + void setStage(int value); + void setGlobalTexAnimValue(); + +private: + void connectGlobalTexAnim(); + void disconnectGlobalTexAnim(); + void updateGlobalTexAnim(int value); + + CWorkspaceNode *_Node; + + NL3D::CPSConstraintMesh *_CM; + + Ui::CConstraintMeshWidget _ui; +}; /* class CConstraintMeshWidget */ + +} /* namespace NLQT */ + +#endif // CONSTRAINT_MESH_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/curve_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/curve_dialog.cpp new file mode 100644 index 000000000..a7c008d01 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/curve_dialog.cpp @@ -0,0 +1,158 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "curve_dialog.h" + +// Project includes +#include "particle_node.h" + +namespace NLQT { + +CurveEditDialog::CurveEditDialog(NL3D::CPSFloatCurveFunctor *curve, CWorkspaceNode *ownerNode, QWidget *parent) + : QDialog(parent) , _Curve(curve), _scale(1.0), _pos(0.0), _Node(ownerNode) +{ + _ui.setupUi(this); + + show(); + _hoverPoints = new HoverPoints(_ui.curveWidget, HoverPoints::CircleShape); + + if (_Curve->hasSmoothing()) + _hoverPoints->setConnectionType(HoverPoints::CurveConnection); + else + _hoverPoints->setConnectionType(HoverPoints::LineConnection); + + _ui.smoothingCheckBox->setChecked(_Curve->hasSmoothing()); + + buildPoints(); + + _hoverPoints->setSortType(HoverPoints::XSort); + _hoverPoints->setShapePen(QPen(QColor(0, 0, 100, 127), 1)); + _hoverPoints->setShapeBrush(QBrush(QColor(0, 0, 200, 127))); + _hoverPoints->setConnectionPen(QPen(QColor(0, 0, 0, 127), 1)); + + // FLOAT_CURVE_NB_SAMPLE + _ui.numSamplesWidget->setRange(1, 512); + _ui.numSamplesWidget->enableLowerBound(1, true); + _ui.numSamplesWidget->setValue(_Curve->getNumSamples()); + + connect(_ui.smoothingCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSmoothing(bool))); + connect(_hoverPoints, SIGNAL(pointsChanged(QPolygonF)), this, SLOT(curveChanged(QPolygonF))); + connect(_ui.posVerticalSlider, SIGNAL(valueChanged(int)), this, SLOT(setPosition(int))); + connect(_ui.sizeVerticalSlider, SIGNAL(valueChanged(int)), this, SLOT(setScale(int))); + connect(_ui.firstLastPushButton, SIGNAL(clicked()), this, SLOT(setLastTiFirst())); + connect(_ui.lastFirstPushButton, SIGNAL(clicked()), this, SLOT(setFirstToLast())); + connect(_ui.centerToolButton, SIGNAL(clicked()), this, SLOT(setCenter())); + connect(_ui.numSamplesWidget, SIGNAL(valueChanged(uint32)), this, SLOT(setNumSamples(uint32))); + //setCenter(); +} + +CurveEditDialog::~CurveEditDialog() +{ +} + +void CurveEditDialog::setSmoothing(bool state) +{ + if (state) + _hoverPoints->setConnectionType(HoverPoints::CurveConnection); + else + _hoverPoints->setConnectionType(HoverPoints::LineConnection); + + _Curve->enableSmoothing(state); + + _ui.curveWidget->update(); +} + +void CurveEditDialog::setDisplayInterpolation(bool state) +{ +} + +void CurveEditDialog::curveChanged(const QPolygonF &points) +{ + if (_Curve->getNumCtrlPoints() > uint(points.size())) + _Curve->removeCtrlPoint(0); + else if (_Curve->getNumCtrlPoints() < uint(points.size())) + _Curve->addControlPoint(NL3D::CPSFloatCurveFunctor::CCtrlPoint(1, 0.5f)); + for (int i = 0; i < points.size(); i++) + _Curve->setCtrlPoint(uint(i), NL3D::CPSFloatCurveFunctor::CCtrlPoint(points.at(i).x() / _ui.curveWidget->width(), + (_ui.curveWidget->height() - points.at(i).y() + _pos) / (_ui.curveWidget->height() * _scale))); +} + +void CurveEditDialog::setScale(int value) +{ + _scale = value / float(_ui.sizeVerticalSlider->maximum() / 2); + buildPoints(); + _ui.curveWidget->update(); +} + +void CurveEditDialog::setPosition(int value) +{ + _pos = (value - (_ui.posVerticalSlider->maximum() / 2)); + buildPoints(); + _ui.curveWidget->update(); +} + +void CurveEditDialog::setCenter() +{ + float maxValue = _Curve->getMaxValue(); + float minValue = _Curve->getMinValue(); + float fScale = (maxValue == minValue) ? 1.f : 1.f / (maxValue - minValue); + int iScale = int(fScale * _ui.sizeVerticalSlider->maximum() / 2); + _ui.sizeVerticalSlider->setValue(iScale); + int pos = _ui.curveWidget->height() - (_scale * maxValue * _ui.curveWidget->height()); + _ui.posVerticalSlider->setValue((_ui.posVerticalSlider->maximum() / 2) - pos); +} + +void CurveEditDialog::setLastTiFirst() +{ + NL3D::CPSFloatCurveFunctor::CCtrlPoint pt = _Curve->getControlPoint(_Curve->getNumCtrlPoints() - 1); + pt.Date = _Curve->getControlPoint(0).Date; + _Curve->setCtrlPoint(0, pt); + buildPoints(); + _ui.curveWidget->update(); +} + +void CurveEditDialog::setFirstToLast() +{ + NL3D::CPSFloatCurveFunctor::CCtrlPoint pt = _Curve->getControlPoint(0); + pt.Date = _Curve->getControlPoint(_Curve->getNumCtrlPoints() - 1).Date; + _Curve->setCtrlPoint(_Curve->getNumCtrlPoints() - 1, pt); + buildPoints(); + _ui.curveWidget->update(); +} + +void CurveEditDialog::setNumSamples(uint32 value) +{ + _Curve->setNumSamples(value); +} + +void CurveEditDialog::buildPoints() +{ + QPolygonF points; + for (uint i = 0; i < _Curve->getNumCtrlPoints(); i++) + points << QPointF((_Curve->getControlPoint(i).Date * _ui.curveWidget->width()), + _pos + _ui.curveWidget->height() - (_scale * _Curve->getControlPoint(i).Value * _ui.curveWidget->height())); + + _hoverPoints->setPoints(points); + //_hoverPoints->setPointLock(0, HoverPoints::LockToLeft); + //_hoverPoints->setPointLock(points.size() - 1, HoverPoints::LockToRight); + +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/curve_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/curve_dialog.h new file mode 100644 index 000000000..6f113c843 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/curve_dialog.h @@ -0,0 +1,83 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef CURVE_DIALOG_H +#define CURVE_DIALOG_H + +#include +#include "ui_curve_form.h" + +// NeL includes +#include + +// Qt includes +#include +#include +#include +#include + +// Project includes +#include "hoverpoints.h" +#include "ps_wrapper.h" + +namespace NLQT { +/** +@class CurveEditDialog +@brief Dialogue editing graphics curve. +@details Adding / removing control points, scaling and moving graphics curve. +The choice of the interpolation algorithm. +*/ +class CurveEditDialog : public QDialog +{ + Q_OBJECT + +public: + CurveEditDialog(NL3D::CPSFloatCurveFunctor *curve, CWorkspaceNode *ownerNode, QWidget *parent = 0); + ~CurveEditDialog(); + + /// the scheme being edited + NL3D::CPSFloatCurveFunctor *_Curve; + +private Q_SLOTS: + void setSmoothing(bool state); + void setDisplayInterpolation(bool state); + void curveChanged(const QPolygonF &points); + void setScale(int value); + void setPosition(int value); + void setCenter(); + void setLastTiFirst(); + void setFirstToLast(); + void setNumSamples(uint32 value); + +protected: + void buildPoints(); + + float _scale; + float _pos; + + CWorkspaceNode *_Node; + + HoverPoints *_hoverPoints; + + Ui::CurveEditDialog _ui; +}; /* CurveEditDialog */ + +} /* namespace NLQT */ + +#endif // CURVE_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/curve_form.ui b/code/nel/tools/3d/object_viewer_qt/src/curve_form.ui new file mode 100644 index 000000000..5825da66c --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/curve_form.ui @@ -0,0 +1,253 @@ + + + CurveEditDialog + + + + 0 + 0 + 528 + 478 + + + + Curve edit + + + true + + + + + + + 0 + 0 + + + + Left click to add points. +Right click to remove points. + + + false + + + + + + + + + Zoom: + + + + + + + Scale curve + + + 10000 + + + 1000 + + + 5000 + + + Qt::Horizontal + + + true + + + false + + + QSlider::TicksBelow + + + 1000 + + + + + + + Translate: + + + + + + + Position curve + + + 5000 + + + 500 + + + 2500 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 500 + + + + + + + + + Num samples: + + + + + + + false + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + + :/images/refresh.png:/images/refresh.png + + + true + + + + + + + + + + + + + 0 + 0 + + + + Last=First + + + + + + + + 0 + 0 + + + + First=Last + + + + + + + false + + + Display interpolation + + + + + + + Smoothing + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + Ok + + + + + + + + + + NLQT::CEditRangeUIntWidget + QWidget +
edit_range_widget.h
+ 1 +
+
+ + + + + + pushButton + clicked() + CurveEditDialog + accept() + + + 463 + 401 + + + 373 + 402 + + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.cpp new file mode 100644 index 000000000..292e05a14 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.cpp @@ -0,0 +1,48 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "day_night_dialog.h" + +// NeL incldes +#include "nel/3d/u_water.h" + +// Project includes +#include "modules.h" + +namespace NLQT { + +CDayNightDialog::CDayNightDialog(QWidget *parent) + : QDockWidget(parent) +{ + _ui.setupUi(this); + + connect(_ui.horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setDayNight(int))); +} + +CDayNightDialog::~CDayNightDialog() +{ +} + +void CDayNightDialog::setDayNight(int value) +{ + NL3D::UWaterHeightMapManager::setBlendFactor(Modules::objView().getDriver(), float(value) / _ui.horizontalSlider->maximum()); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.h new file mode 100644 index 000000000..e7f382076 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/day_night_dialog.h @@ -0,0 +1,56 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef DAY_NIGHT_DIALOG_H +#define DAY_NIGHT_DIALOG_H + +#include "ui_day_night_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NLQT { + +/** +@class CDayNightDialog +@brief Dialog set time of day. +@details Dialogue only affects the water shape. +*/ +class CDayNightDialog: public QDockWidget +{ + Q_OBJECT + +public: + CDayNightDialog(QWidget *parent = 0); + ~CDayNightDialog(); + +private Q_SLOTS: + void setDayNight(int value); + +private: + + Ui::CDayNightDialog _ui; +}; /* class CDayNightDialog */ + +} /* namespace NLQT */ + +#endif // DAY_NIGHT_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/day_night_form.ui b/code/nel/tools/3d/object_viewer_qt/src/day_night_form.ui new file mode 100644 index 000000000..c3508db12 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/day_night_form.ui @@ -0,0 +1,47 @@ + + + CDayNightDialog + + + + 0 + 0 + 228 + 59 + + + + Day/Night + + + + + + + Day + + + + + + + 100 + + + Qt::Horizontal + + + + + + + Night + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/description.h b/code/nel/tools/3d/object_viewer_qt/src/description.h new file mode 100644 index 000000000..f1e3fc8a7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/description.h @@ -0,0 +1,145 @@ +/** +@mainpage Object Viewer Qt +@author Dzmitry Kamiahin , (C) 2010 + +@section introduce Introduce +@details +The Object Viewer Qt (ovqt) is a tool for working with graphics technology NeL data, +which can be useful durung the development and preparation of the game content. +The tool can be used in different tasks: +- Loading and viewing NeL data (.shape, .ps) files with the ability to set wind power, color light. +- Viewing animation shape (loading .anim, .skel, .swt files) using playlist or slot manager(mixing animation). +- Create and modify NeL particle system. +- Viewing water shape with the ability to set day/night. +- Modify skeleton (.skel files, .scale file). +- Create and modify micro-vegetation material (.vegetset files) +- Viewing landscape (.zonel files) +- Dialog allows to specify graphical, sound, search path and landscape settings. + +@section project_structure Overview of the Object Viewer Qt Project Structure +@details +OVQT - consists of several major subsystems. Each subsystem performs its defined part of the task. +Through Modules:: provides access to all other program subsystems. + +Program has the following subsystems: +- @ref Modules - Main modules aggregated all parts of the program. +- @ref NLQT::CConfiguration - is responsible for loading and saving settings from the configuration file. As well as search path of data. +- @ref NLQT::CObjectViewer - main subsystem of the program, which initializes the driver, creates a scene and other supporting elements. +It is a container for loaded models, which can further be viewed and animated. +- @ref NLQT::CMainWindow - is responsible for the GUI. +- @ref NLQT::CParticleEditor - is responsible for the particle systems and provides access to a container that keeps all the loaded particle systems. +And also allows you to view an animation of particle systems, with the ability to control its parameters. +- @ref NLQT::CVegetableEditor - is responsible for the landscape zones and the editor of vegetation. +Allows you to load and view the landscape. Also has the ability to create and edit the parameters of the micro-vegetation. +- @ref NLQT::CSoundSystem - is responsible for the sound in the program. + + +The structure of the GUI in the editor of the particles. +
+This can be useful for new developers who want to add more new dialogs or to improve the functionality of existing dialogues. +Particle Workspace +ParticleWorkspace dialogue uses the technique of model-view. +Using the signal/slot link QTreeView with QStackWidget, Editor properties dialog. +When you select an item in QTreeView, QStackWidget displays the necessary page(PageWidget) where you can edit the element of the particles system. +Each page is made in the designer and has a separate forms(.ui). +In accordance with the recommendations of dialogues design, with a large number of items,every page uses QTabWidget. +In case when there is a great number of controls, and not everything you want to display, +it's used the dynamic creation of tabs. Moreover, each tab uses a separate forms(.ui). + +@section for_new_developer Guide for new developers of the Object Viewer Qt. +@details + First of all, to begin developing dialogues that add new features ovqt, +it is needed to read the documentation Qt libs (http://doc.qt.nokia.com/) and NeL documentation. + + In order to have convenient using of the tool and its further development, +it is expected to make a unified interface that is why all dialogs should adhere to a standard design, +which will be written further. For this goals program provides some additional widgets, +which are recommended to use. As in the development Qt Designer is actively used. +To get access to founded here widgets from the designer,the technique promotion is used, +which can be found here . +
    +
  1. +CEditRangeUIntWidget / CEditRangeIntWidget / CEditRangeFloatWidget +
    +Widgets provides a slider that allows you to specify an integer (or float, depending on which widget used) number within a set range. +The range is also can be set by the user, or for a more accurate selection of numbers, either for receiving large values. +As there may be situations when the range that a user requests,has to be restricted, and widget provides methods that allow you to do so. +

    +CEditRangeIntWidget +
    +CEditRangeFloatWidget +@see +@ref NLQT::CEditRangeUIntWidget, @ref NLQT::CEditRangeIntWidget, @ref NLQT::CEditRangeFloatWidget + +
  2. +CColorEditWidget +
    +Using this widget you can set the color(RGBA) using the four sliders or through the color selection dialog. +

    +CColorEditWidget +@see +@ref NLQT::CColorEditWidget + +
  3. +CDirectionWidget +
    +This widget helps to choose from several preset directions, or to choose a custom one. +To use it you have to create a wrapper. +

    +CDirectionWidget +@see +@ref NLQT::CDirectionWidget +


+ +To further convinience of instrument using dialogues interface is recommended to make as follows. +The most sophisticated tools of the program, should take the form of three dock windows (example shown in the screenshot). +Object Viewer Qt +
    +
  1. +The first dock window - is a control dock window, it should focus all of the control functions of general purpose +(for example: start/stop/pause animations or particles system). +It is recommended to perform of the horizontal type window and placed in the bottom of the main window. +
  2. +The second dock window - is a list or a tree of items. In which selecting of the current element, +which assumes to modify or viewis possible.Operations "add/remove/insert or other" items are recommended to make as a popur menu. +It is recommended to perform of the vertical type window and placed in the left of the main window. +
  3. +The third dock - is an editor for properties of the element that was selected in the list or in the tree of elements. +As all the controls occupy much space or do not fit at all, you have to use tabs QTabWidget, +which in total can contains quite a lot of elements. +For a small number of tabs it is allowed to use both horizontal and vertical location. +But with a large number of tabs, it is necessary to apply the vertical arrangement. +It is recommended to perform of the vertical type of window and placed in the right of the main window. +
+In the simple dialogues do not necessary to use all three windows, but user has to adhere to the recommendations given above as well. +Also, all dialogs must use the qt layout manager. And if you do not use the designer, +make sure you use the qt tools internationalization applications. +

+In ovqt for most dialogs their owner is NLQT::CMainWindow and in its methods creating and initializing all dependent dialogs occur. +For the convenience of the program using, most dialogues are created in the form of docking windows. +Creating all the dialogues are carried out in a private method NLQT::CMainWindow::createDialogs(). +Hence, it is necessary to add operations in this method to create new dialogues. +But we must take into account that at this moment is fully available only one component of the program CConfiguration, +to read the settings from the configuration file. The remaining components of the program are available only after the main window stays visible. +Calling the dialogues going through the menu or the toolbar, usually it is a checkable item. +Adding of the new menu items or items toolbars is need in methods NLQT::CMainWindow::createMenus(), NLQT::CMainWindow::createToolBars(). + +@section license_ovqt License Object Viewer Qt +@details + Object Viewer Qt +
+ Copyright (C) 2010 Dzmitry Kamiahin +

+ This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. +

+ This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +

+ You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/direction_form.ui b/code/nel/tools/3d/object_viewer_qt/src/direction_form.ui new file mode 100644 index 000000000..f04c7302b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/direction_form.ui @@ -0,0 +1,178 @@ + + + CDirectionWidget + + + + 0 + 0 + 268 + 101 + + + + Form + + + + + + Direction: + + + + + + + + 30 + 16777215 + + + + +I + + + + + + + + 30 + 16777215 + + + + +J + + + + + + + + 30 + 16777215 + + + + +K + + + + + + + + 0 + 0 + + + + + 75 + 75 + + + + + 75 + 75 + + + + + + + + + 0 + 0 + + + + + 75 + 75 + + + + + 75 + 75 + + + + + + + + Qt::Horizontal + + + + 0 + 49 + + + + + + + + + 30 + 16777215 + + + + -I + + + + + + + + 30 + 16777215 + + + + -J + + + + + + + + 30 + 16777215 + + + + -K + + + + + + + Global + + + + + + + + NLQT::CGraphicsInfoWidget + QWidget +
graphics_info_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/direction_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/direction_widget.cpp new file mode 100644 index 000000000..49521fb15 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/direction_widget.cpp @@ -0,0 +1,217 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "direction_widget.h" + +// Qt includes +#include + +// NeL includes +#include +#include + +namespace NLQT { + +CDirectionWidget::CDirectionWidget(QWidget *parent) + : QWidget(parent), _Wrapper(NULL), _DirectionWrapper(NULL) +{ + _ui.setupUi(this); + + _ui.xzWidget->setMode(Mode::Direction); + _ui.yzWidget->setMode(Mode::Direction); + _ui.xzWidget->setText("XZ"); + _ui.yzWidget->setText("YZ"); + _ui.globalPushButton->hide(); + + connect(_ui.globalPushButton ,SIGNAL(clicked()), this, SLOT(setGlobalDirection())); + connect(_ui.incVecIPushButton ,SIGNAL(clicked()), this, SLOT(incVecI())); + connect(_ui.incVecJPushButton ,SIGNAL(clicked()), this, SLOT(incVecJ())); + connect(_ui.incVecKPushButton ,SIGNAL(clicked()), this, SLOT(incVecK())); + connect(_ui.decVecIPushButton ,SIGNAL(clicked()), this, SLOT(decVecI())); + connect(_ui.decVecJPushButton ,SIGNAL(clicked()), this, SLOT(decVecJ())); + connect(_ui.decVecKPushButton ,SIGNAL(clicked()), this, SLOT(decVecK())); + + connect(_ui.xzWidget, SIGNAL(applyNewVector(float,float)), this, SLOT(setNewVecXZ(float,float))); + connect(_ui.yzWidget, SIGNAL(applyNewVector(float,float)), this, SLOT(setNewVecYZ(float,float))); +} + +CDirectionWidget::~CDirectionWidget() +{ +} + + +void CDirectionWidget::setWrapper(IPSWrapper *wrapper) +{ + _Wrapper = wrapper; + _ui.globalPushButton->hide(); +} + +void CDirectionWidget::setDirectionWrapper(NL3D::CPSDirection *wrapper) +{ + _DirectionWrapper = wrapper; + if (_DirectionWrapper && _DirectionWrapper->supportGlobalVectorValue()) + _ui.globalPushButton->show(); + else + _ui.globalPushButton->hide(); +} + +void CDirectionWidget::updateUi() +{ + _ui.xzWidget->setVector(_Wrapper->get().x, _Wrapper->get().z); + _ui.yzWidget->setVector(_Wrapper->get().y, _Wrapper->get().z); + _ui.xzWidget->repaint(); + _ui.yzWidget->repaint(); + + checkEnabledGlobalDirection(); +} + +void CDirectionWidget::setGlobalDirection() +{ + nlassert(_DirectionWrapper); + + bool ok; + QString text = QInputDialog::getText(this, tr("Enter Name"), + "", QLineEdit::Normal, + QString(_DirectionWrapper->getGlobalVectorValueName().c_str()), &ok); + + if (ok) + { + _DirectionWrapper->enableGlobalVectorValue(text.toStdString()); + if (!text.isEmpty()) + { + // take a non NULL value for the direction + NL3D::CParticleSystem::setGlobalVectorValue(text.toStdString(), NLMISC::CVector::I); + } + checkEnabledGlobalDirection(); + } +} + +void CDirectionWidget::incVecI() +{ + _Wrapper->setAndUpdateModifiedFlag(NLMISC::CVector::I); + _ui.xzWidget->setVector(NLMISC::CVector::I.x, NLMISC::CVector::I.z); + _ui.yzWidget->setVector(NLMISC::CVector::I.y, NLMISC::CVector::I.z); +} + +void CDirectionWidget::incVecJ() +{ + _Wrapper->setAndUpdateModifiedFlag(NLMISC::CVector::J); + _ui.xzWidget->setVector(NLMISC::CVector::J.x, NLMISC::CVector::J.z); + _ui.yzWidget->setVector(NLMISC::CVector::J.y, NLMISC::CVector::J.z); +} + +void CDirectionWidget::incVecK() +{ + _Wrapper->setAndUpdateModifiedFlag(NLMISC::CVector::K); + _ui.xzWidget->setVector(NLMISC::CVector::K.x, NLMISC::CVector::K.z); + _ui.yzWidget->setVector(NLMISC::CVector::K.y, NLMISC::CVector::K.z); +} + +void CDirectionWidget::decVecI() +{ + _Wrapper->setAndUpdateModifiedFlag( - NLMISC::CVector::I); + _ui.xzWidget->setVector((-NLMISC::CVector::I).x, (-NLMISC::CVector::I).z); + _ui.yzWidget->setVector((-NLMISC::CVector::I).y, (-NLMISC::CVector::I).z); +} + +void CDirectionWidget::decVecJ() +{ + _Wrapper->setAndUpdateModifiedFlag( - NLMISC::CVector::J); + _ui.xzWidget->setVector((-NLMISC::CVector::J).x, (-NLMISC::CVector::J).z); + _ui.yzWidget->setVector((-NLMISC::CVector::J).y, (-NLMISC::CVector::J).z); +} + +void CDirectionWidget::decVecK() +{ + _Wrapper->setAndUpdateModifiedFlag( - NLMISC::CVector::K); + _ui.xzWidget->setVector((-NLMISC::CVector::K).x, (-NLMISC::CVector::K).z); + _ui.yzWidget->setVector((-NLMISC::CVector::K).y, (-NLMISC::CVector::K).z); +} + +void CDirectionWidget::setNewVecXZ(float x, float y) +{ + const float epsilon = 10E-3f; + NLMISC::CVector v = _Wrapper->get(); + + v.x = x; + v.z = y; + + float d = v.x * v.x + v.z * v.z; + float f; + if (fabs(d) > epsilon) + f = sqrt((1.f - v.y * v.y) / d); + else + f = 1; + + v.x *= f; + v.z *= f; + + v.normalize(); + + _Wrapper->setAndUpdateModifiedFlag(v); + _ui.xzWidget->setVector(_Wrapper->get().x, _Wrapper->get().z); + _ui.yzWidget->setVector(_Wrapper->get().y, _Wrapper->get().z); +} + +void CDirectionWidget::setNewVecYZ(float x, float y) +{ + const float epsilon = 10E-3f; + NLMISC::CVector v = _Wrapper->get(); + + v.y = x; + v.z = y; + + float d = v.y * v.y + v.z * v.z; + float f; + if (fabs(d) > epsilon) + f = sqrt((1.f - v.x * v.x) / d); + else + f = 1; + + v.y *= f; + v.z *= f; + + v.normalize(); + + _Wrapper->setAndUpdateModifiedFlag(v); + _ui.xzWidget->setVector(_Wrapper->get().x, _Wrapper->get().z); + _ui.yzWidget->setVector(_Wrapper->get().y, _Wrapper->get().z); +} + +void CDirectionWidget::checkEnabledGlobalDirection() +{ + bool enableUserDirection = true; + _ui.xzWidget->show(); + _ui.yzWidget->show(); + if (_DirectionWrapper && _DirectionWrapper->supportGlobalVectorValue() && !_DirectionWrapper->getGlobalVectorValueName().empty()) + { + enableUserDirection = false; + _ui.xzWidget->hide(); + _ui.yzWidget->hide(); + } + _ui.incVecIPushButton->setEnabled(enableUserDirection); + _ui.incVecJPushButton->setEnabled(enableUserDirection); + _ui.incVecKPushButton->setEnabled(enableUserDirection); + _ui.decVecIPushButton->setEnabled(enableUserDirection); + _ui.decVecJPushButton->setEnabled(enableUserDirection); + _ui.decVecKPushButton->setEnabled(enableUserDirection); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/direction_widget.h b/code/nel/tools/3d/object_viewer_qt/src/direction_widget.h new file mode 100644 index 000000000..3125bfe38 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/direction_widget.h @@ -0,0 +1,78 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef DIRECTION_WIDGET_H +#define DIRECTION_WIDGET_H + +#include "ui_direction_form.h" + +// STL includes + +// NeL includes +#include + +// Project includes +#include "ps_wrapper.h" + +namespace NLQT { + +/** +@class CDirectionWidget +@brief This widget helps to choose from several preset directions, or to choose a custom one. +@details This also allow to bind the direction to a global variable when it is supported + */ +class CDirectionWidget: public QWidget +{ + Q_OBJECT + +public: + CDirectionWidget(QWidget *parent = 0); + ~CDirectionWidget(); + + void setWrapper(IPSWrapper *wrapper); + + /// The CPSDirection object is used to see if a global variable can be bound to the direction. + /// When set to NULL it has no effect (the default) + void setDirectionWrapper(NL3D::CPSDirection *wrapper); + void updateUi(); + +private Q_SLOTS: + void setGlobalDirection(); + void incVecI(); + void incVecJ(); + void incVecK(); + void decVecI(); + void decVecJ(); + void decVecK(); + void setNewVecXZ(float x, float y); + void setNewVecYZ(float x, float y); + +private: + void checkEnabledGlobalDirection(); + + IPSWrapper *_Wrapper ; + NL3D::CPSDirection *_DirectionWrapper; + + Ui::CDirectionWidget _ui; + +}; /* class CDirectionWidget */ + +} /* namespace NLQT */ + +#endif // DIRECTION_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/dup_ps.cpp b/code/nel/tools/3d/object_viewer_qt/src/dup_ps.cpp new file mode 100644 index 000000000..91e0c1609 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/dup_ps.cpp @@ -0,0 +1,161 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" + +#include "nel/3d/ps_located.h" +#include "nel/3d/particle_system.h" + + +#include "nel/misc/stream.h" +#include "nel/misc/mem_stream.h" + +#include "dup_ps.h" + +#include + +using namespace NL3D; + +//======================================================================= + + + +/** This can duplicate any serializable type by using a serialization policy (polymorphic, non polymorphic ..) + * The serialization policy must have a method to serial a pointer on the object (see example below) + * NB : of course this is slow (but convenient) way of performing a copy + * TODO maybe this could be used elsewhere ? + */ +template +static T *DupSerializable(const T *in) throw(NLMISC::EStream) +{ + NLMISC::CMemStream ms; + nlassert(!ms.isReading()); + T *nonConstIn = const_cast(in); + TSerializePolicy::serial(nonConstIn, ms); + std::vector datas(ms.length()); + std::copy(ms.buffer(), ms.buffer() + ms.length(), datas.begin()); + ms.resetPtrTable(); + ms.invert(); + ms.fill(&datas[0], (uint)datas.size()); + nlassert(ms.isReading()); + T *newObj = NULL; + TSerializePolicy::serial(newObj, ms); + return newObj; +} + +/** A policy to duplicate a non-polymorphic type + */ +struct CDupObjPolicy +{ + template + static void serial(T *&obj, NLMISC::IStream &dest) throw(NLMISC::EStream) + { + dest.serialPtr(obj); + /*if (dest.isReading()) + { + std::auto_ptr newObj(new T); + newObj->serialPtr(dest); + delete obj; + obj = newObj.release(); + } + else + { + obj->serial(dest); + }*/ + } +}; + +/** A policy to duplicate a polymorphic type + */ +struct CDupPolymorphicObjPolicy +{ + template + static void serial(T *&obj, NLMISC::IStream &dest) throw(NLMISC::EStream) + { + dest.serialPolyPtr(obj); + } +}; + + + +//======================================================================= +///////////////////////////////////////// +// temp until there is a clone method // +///////////////////////////////////////// +NL3D::CParticleSystemProcess *DupPSLocated(const CParticleSystemProcess *in) +{ + if (!in) return NULL; + try + { + // if the located doesn't belon to a system, copy it direclty + if (in->getOwner() == NULL) + { + return DupSerializable(in); + } + else + { + uint index = in->getOwner()->getIndexOf(*in); + /** Duplicate the system, and detach. + * We can't duplicate the object direclty (it may be referencing other objects in the system, so these objects will be copied too...) + */ + std::auto_ptr newPS(DupSerializable(in->getOwner())); + // scene pointer is not serialised, but 'detach' may need the scene to be specified + newPS->setScene(in->getOwner()->getScene()); + return newPS->detach(index); + } + } + catch (NLMISC::EStream &e) + { + nlwarning (e.what()); + return NULL; + } +} + +//======================================================================= +///////////////////////////////////////// +// temp until there is a clone method // +///////////////////////////////////////// +NL3D::CPSLocatedBindable *DupPSLocatedBindable(CPSLocatedBindable *in) +{ + if (!in) return NULL; + try + { + // if no owner, can copy the object directy + if (in->getOwner() == NULL) + { + return DupSerializable(in); + } + else + { + CParticleSystem *srcPS = in->getOwner()->getOwner(); + std::auto_ptr newPS(DupSerializable(srcPS)); + // scene pointer is not serialised, but 'detach' may need the scene to be specified + newPS->setScene(in->getOwner()->getOwner()->getScene()); + // + uint index = srcPS->getIndexOf(*(in->getOwner())); + uint subIndex = in->getOwner()->getIndexOf(in); + // + newPS->setScene(in->getOwner()->getScene()); // 'unbind' require the scene to be attached + CPSLocated *loc = NLMISC::safe_cast(newPS->getProcess(index)); + return loc->unbind(subIndex); + } + } + catch (NLMISC::EStream &e) + { + nlwarning (e.what()); + return NULL; + } +} diff --git a/code/nel/tools/3d/object_viewer_qt/src/dup_ps.h b/code/nel/tools/3d/object_viewer_qt/src/dup_ps.h new file mode 100644 index 000000000..da79b6374 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/dup_ps.h @@ -0,0 +1,52 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + + + +#ifndef NL_DUP_PS_H +#define NL_DUP_PS_H + + +namespace NL3D +{ + class CParticleSystemProcess; + class CPSLocatedBindable; +} + + +/** For now there is no duplication method in particle system. + * These are needed for edition, though.. + * These functions provide a way to do this (by serializing the system in a memory stream). + * NB this is slow + * These may be removed, but this provide a easy way to accomplish our goal + */ + +/** temp : duplicate a process of a particle system. + * return NULL if the copy failed + */ +NL3D::CParticleSystemProcess *DupPSLocated(const NL3D::CParticleSystemProcess *in); + +/** temp : duplicate a located bindable of a particle system + * return NULL if the copy failed + */ +NL3D::CPSLocatedBindable *DupPSLocatedBindable(NL3D::CPSLocatedBindable *in); + + + + + +#endif \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/edit_range_float_form.ui b/code/nel/tools/3d/object_viewer_qt/src/edit_range_float_form.ui new file mode 100644 index 000000000..7f0a631e7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/edit_range_float_form.ui @@ -0,0 +1,108 @@ + + + CEditRangeFloatWidget + + + + 0 + 0 + 304 + 32 + + + + Form + + + + + + QAbstractSpinBox::NoButtons + + + false + + + 2 + + + -9999.000000000000000 + + + 9999.000000000000000 + + + + + + + + 50 + 0 + + + + 0 + + + 1000000 + + + Qt::Horizontal + + + + + + + QAbstractSpinBox::NoButtons + + + false + + + 2 + + + -9999.000000000000000 + + + 9999.000000000000000 + + + + + + + true + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 2 + + + -9999.000000000000000 + + + 9999.000000000000000 + + + 0.000000000000000 + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/edit_range_uint_form.ui b/code/nel/tools/3d/object_viewer_qt/src/edit_range_uint_form.ui new file mode 100644 index 000000000..d9878254e --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/edit_range_uint_form.ui @@ -0,0 +1,108 @@ + + + CEditRangeUIntWidget + + + + 0 + 0 + 294 + 32 + + + + Form + + + + + + QAbstractSpinBox::NoButtons + + + false + + + 99998 + + + + + + + 100 + + + Qt::Horizontal + + + + + + + QAbstractSpinBox::NoButtons + + + false + + + 99999 + + + + + + + false + + + QAbstractSpinBox::NoButtons + + + false + + + 99999 + + + + + + + + + + + horizontalSlider + valueChanged(int) + currentSpinBox + setValue(int) + + + 104 + 30 + + + 213 + 34 + + + + + currentSpinBox + valueChanged(int) + horizontalSlider + setValue(int) + + + 206 + 29 + + + 91 + 27 + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.cpp new file mode 100644 index 000000000..4099f03fa --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.cpp @@ -0,0 +1,363 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "edit_range_widget.h" + +// NeL includes + +// Project includes +#include "ps_wrapper.h" + +using namespace NL3D; +using namespace NLMISC; + +namespace NLQT { + +const int max_range = 9999; + +CEditRangeUIntWidget::CEditRangeUIntWidget(QWidget *parent) + : QWidget(parent), _Wrapper(NULL), _emit(true) +{ + _ui.setupUi(this); + + _ui.endSpinBox->setMinimum(0); + _ui.startSpinBox->setMinimum(0); + _ui.currentSpinBox->setMinimum(0); + + connect(_ui.startSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setMinimum(int))); + connect(_ui.endSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setMaximum(int))); + connect(_ui.horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(changeSlider(int))); + + setValue(0, false); +} + +CEditRangeUIntWidget::~CEditRangeUIntWidget() +{ +} + +void CEditRangeUIntWidget::setWrapper(IPSWrapperUInt *wrapper) +{ + _Wrapper = wrapper; +} + +void CEditRangeUIntWidget::setRange(uint32 minValue, uint32 maxValue) +{ + setRangeMin(minValue); + setRangeMax(maxValue); +} + +void CEditRangeUIntWidget::setRangeMin(uint32 minValue) +{ + _ui.startSpinBox->setValue(minValue); +} + +void CEditRangeUIntWidget::setRangeMax(uint32 maxValue) +{ + _ui.endSpinBox->setValue(maxValue); +} + +void CEditRangeUIntWidget::setValue(uint32 value, bool emit) +{ + if (value > uint32(_ui.endSpinBox->value())) + _ui.endSpinBox->setValue(value); + if (value < uint32(_ui.startSpinBox->value())) + _ui.startSpinBox->setValue(value); + + _emit = emit; + _ui.horizontalSlider->setValue(value); + _emit = true; +} + +void CEditRangeUIntWidget::enableUpperBound(uint32 upperBound, bool upperBoundExcluded) +{ + if (upperBoundExcluded) + upperBound--; + _ui.endSpinBox->setMaximum(upperBound); + _ui.startSpinBox->setMaximum(upperBound); +} + +void CEditRangeUIntWidget::enableLowerBound(uint32 lowerBound, bool lowerBoundExcluded) +{ + if (lowerBoundExcluded) + lowerBound++; + _ui.endSpinBox->setMinimum(lowerBound); + _ui.startSpinBox->setMinimum(lowerBound); +} + +void CEditRangeUIntWidget::disableUpperBound(void) +{ + _ui.endSpinBox->setMaximum(max_range); + _ui.startSpinBox->setMaximum(max_range); +} + +void CEditRangeUIntWidget::disableLowerBound(void) +{ + _ui.endSpinBox->setMinimum(0); + _ui.startSpinBox->setMinimum(0); +} + +void CEditRangeUIntWidget::setMaximum(int value) +{ + _ui.horizontalSlider->setMaximum(value); + _ui.currentSpinBox->setMaximum(value); +} + +void CEditRangeUIntWidget::setMinimum(int value) +{ + _ui.horizontalSlider->setMinimum(value); + _ui.currentSpinBox->setMinimum(value); +} + +void CEditRangeUIntWidget::changeSlider(int value) +{ + if (_emit) + Q_EMIT valueChanged(value); + + // NeL wrapper + if (_Wrapper == NULL) return; + if (_Wrapper->get() == uint32(value)) return; + _Wrapper->setAndUpdateModifiedFlag(value); +} + +void CEditRangeUIntWidget::updateUi() +{ + if (_Wrapper == NULL) return; + _ui.horizontalSlider->setSliderPosition(_Wrapper->get()); +} + +CEditRangeIntWidget::CEditRangeIntWidget(QWidget *parent) + : QWidget(parent), _Wrapper(NULL) +{ + _ui.setupUi(this); + connect(_ui.startSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setMinimum(int))); + connect(_ui.endSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setMaximum(int))); + connect(_ui.horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(changeSlider(int))); + + setValue(0, false); +} + +CEditRangeIntWidget::~CEditRangeIntWidget() +{ +} + +void CEditRangeIntWidget::setWrapper(IPSWrapperInt *wrapper) +{ + _Wrapper = wrapper; +} + +void CEditRangeIntWidget::setRange(sint32 minValue, sint32 maxValue) +{ + setRangeMin(minValue); + setRangeMax(maxValue); +} + +void CEditRangeIntWidget::setRangeMin(sint32 minValue) +{ + _ui.startSpinBox->setValue(minValue); +} + +void CEditRangeIntWidget::setRangeMax(sint32 maxValue) +{ + _ui.endSpinBox->setValue(maxValue); +} + +void CEditRangeIntWidget::setValue(sint32 value, bool emit) +{ + if (value > sint32(_ui.endSpinBox->value())) + _ui.endSpinBox->setValue(value); + if (value < sint32(_ui.startSpinBox->value())) + _ui.startSpinBox->setValue(value); + + _emit = emit; + _ui.horizontalSlider->setValue(value); + _emit = true; +} + +void CEditRangeIntWidget::enableUpperBound(sint32 upperBound, bool upperBoundExcluded) +{ + if (upperBoundExcluded) + upperBound--; + _ui.endSpinBox->setMaximum(upperBound); + _ui.startSpinBox->setMaximum(upperBound); +} + +void CEditRangeIntWidget::enableLowerBound(sint32 lowerBound, bool lowerBoundExcluded) +{ + if (lowerBoundExcluded) + lowerBound++; + _ui.endSpinBox->setMinimum(lowerBound); + _ui.startSpinBox->setMinimum(lowerBound); +} + +void CEditRangeIntWidget::disableUpperBound(void) +{ + _ui.endSpinBox->setMaximum(max_range); + _ui.startSpinBox->setMaximum(max_range); +} + +void CEditRangeIntWidget::disableLowerBound(void) +{ + _ui.endSpinBox->setMinimum(-max_range); + _ui.startSpinBox->setMinimum(-max_range); +} + +void CEditRangeIntWidget::setMaximum(int value) +{ + _ui.horizontalSlider->setMaximum(value); + _ui.currentSpinBox->setMaximum(value); +} + +void CEditRangeIntWidget::setMinimum(int value) +{ + _ui.horizontalSlider->setMinimum(value); + _ui.currentSpinBox->setMinimum(value); +} + +void CEditRangeIntWidget::changeSlider(int value) +{ + if (_emit) + Q_EMIT valueChanged(value); + + // NeL wrapper + if (_Wrapper == NULL) return; + if (_Wrapper->get() == sint32(value)) return; + _Wrapper->setAndUpdateModifiedFlag(value); +} + +void CEditRangeIntWidget::updateUi() +{ + if (_Wrapper == NULL) return; + _ui.horizontalSlider->setSliderPosition(_Wrapper->get()); +} + + +CEditRangeFloatWidget::CEditRangeFloatWidget(QWidget *parent ) + : QWidget(parent), _Wrapper(NULL), _emit(true) +{ + _ui.setupUi(this); + + connect(_ui.startSpinBox, SIGNAL(valueChanged(double)), this, SLOT(changeRange())); + connect(_ui.endSpinBox, SIGNAL(valueChanged(double)), this, SLOT(changeRange())); + connect(_ui.horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(changeSlider(int))); + + setValue(0.0, false); +} + +CEditRangeFloatWidget::~CEditRangeFloatWidget() +{ +} + +void CEditRangeFloatWidget::setWrapper(IPSWrapperFloat *wrapper) +{ + _Wrapper = wrapper; +} + +void CEditRangeFloatWidget::setValue(float value, bool emit) +{ + if (value > _ui.endSpinBox->value()) + _ui.endSpinBox->setValue(value); + if (value < _ui.startSpinBox->value()) + _ui.startSpinBox->setValue(value); + + float delta = _ui.endSpinBox->value() - _ui.startSpinBox->value(); + int deltaSlider = _ui.horizontalSlider->maximum() - _ui.horizontalSlider->minimum(); + int newValue = floor((deltaSlider / delta) * (value - _ui.startSpinBox->value())); + _emit = emit; + _ui.horizontalSlider->setSliderPosition(newValue); + _emit = true; +} + +void CEditRangeFloatWidget::setRange(float minValue, float maxValue) +{ + setRangeMax(maxValue); + setRangeMin(minValue); +} + +void CEditRangeFloatWidget::setRangeMin(float minValue) +{ + _ui.startSpinBox->setValue(minValue); +} + +void CEditRangeFloatWidget::setRangeMax(float maxValue) +{ + _ui.endSpinBox->setValue(maxValue); +} + +void CEditRangeFloatWidget::enableUpperBound(float upperBound, bool upperBoundExcluded) +{ + if (upperBoundExcluded) + upperBound -= 0.001f; + _ui.endSpinBox->setMaximum(upperBound); + _ui.startSpinBox->setMaximum(upperBound); +} + +void CEditRangeFloatWidget::enableLowerBound(float lowerBound, bool lowerBoundExcluded) +{ + if (lowerBoundExcluded) + lowerBound += 0.01f; + _ui.endSpinBox->setMinimum(lowerBound); + _ui.startSpinBox->setMinimum(lowerBound); +} + +void CEditRangeFloatWidget::disableUpperBound(void) +{ + _ui.endSpinBox->setMaximum(max_range); + _ui.startSpinBox->setMaximum(max_range); +} + +void CEditRangeFloatWidget::disableLowerBound(void) +{ + _ui.endSpinBox->setMinimum(-max_range); + _ui.startSpinBox->setMinimum(-max_range); +} + +void CEditRangeFloatWidget::changeRange() +{ + if ((_ui.startSpinBox->value() < _ui.currentSpinBox->value()) && + (_ui.endSpinBox->value() > _ui.currentSpinBox->value())) + setValue(_ui.currentSpinBox->value(), false); +} + +void CEditRangeFloatWidget::changeSlider(int value) +{ + float delta = _ui.endSpinBox->value() - _ui.startSpinBox->value(); + int deltaSlider = _ui.horizontalSlider->maximum() - _ui.horizontalSlider->minimum(); + float newValue = _ui.startSpinBox->value() + ((delta / deltaSlider) * value); + _ui.currentSpinBox->setValue(newValue); + if (_emit) + Q_EMIT valueChanged(newValue); + + // NeL wrapper + if (_Wrapper == NULL) return; + if (fabs(newValue - _Wrapper->get()) < 0.0001) return; + _Wrapper->setAndUpdateModifiedFlag(newValue); +} + +void CEditRangeFloatWidget::updateUi() +{ + if (_Wrapper == NULL) return; + _ui.currentSpinBox->setValue(_Wrapper->get()); + float delta = _ui.endSpinBox->value() - _ui.startSpinBox->value(); + int deltaSlider = _ui.horizontalSlider->maximum() - _ui.horizontalSlider->minimum(); + int newValue = floor((deltaSlider / delta) * (_Wrapper->get() - _ui.startSpinBox->value())); + _ui.horizontalSlider->setSliderPosition(newValue); +} + +} /* namespace NLQT */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.h b/code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.h new file mode 100644 index 000000000..32a4e0a98 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/edit_range_widget.h @@ -0,0 +1,290 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef EDIT_RANGE_WIDGET_H +#define EDIT_RANGE_WIDGET_H + +#include "ui_edit_range_float_form.h" +#include "ui_edit_range_uint_form.h" + +// Qt includes + +// Project includes +#include "ps_wrapper.h" + +namespace NLQT { + +/** +@class CEditRangeUIntWidget +@brief The widget provides a horizontal slider and 3 QSpinBox(to set start/end value range and сurrent value from this range.). +@details Slider sets the uint32 value within a specified range (start/end QSpinBox). +The values range can be set through the class methods: setRange() or setRangeMin(), setRangeMax(). +Or the user input values in the widgets start/end QSpinBox. +Also the range of start/end values can be restricted through the class methods: enableLowerBound(), enableUpperBound() +this widget can be used by a two ways: Qt Q_SIGNAL/SLOT or wrapper. + +1. Using the Qt Q_SIGNAL/SLOT current value can be set by class methods setValue() and +changes in current value(QSlider or QSpinBox) the signal valueChanged() will be emitted. + +2. Using wrapper, create wpapper struct, example: +@code +struct CMaxNbParticlesWrapper : public IPSWrapperUInt +{ + NL3D::CPSLocated *Located; + uint32 get(void) const { return Located->getMaxSize(); } + void set(const uint32 &v) { Located->setMaxSize(v); } +} _MaxNbParticlesWrapper; +@endcode +to set the current values,it is need to call class methods updateUi(); +*/ + +class CEditRangeUIntWidget: public QWidget +{ + Q_OBJECT + +public: + /// Constructor, sets 0 default current value + CEditRangeUIntWidget(QWidget *parent = 0); + ~CEditRangeUIntWidget(); + + /// Set an interface of a wrapper to read / write values in the particle system + /// NB : The 'OwnerNode' field of the wrapper + void setWrapper(IPSWrapperUInt *wrapper); + + /// Set current value + /// @param value - current value + /// @param emit - will emit valueChanged() if the new value is different from the old one and param emit = true + void setValue(uint32 value, bool emit = true); + + /// Convenience function to set the minimum, and maximum values with a single function call + void setRange(uint32 minValue, uint32 maxValue); + + /// Set the minimum value that can take range(slider) + void setRangeMin(uint32 minValue); + + /// Set the maximum value that can take range(slider) + void setRangeMax(uint32 maxValue); + + /// Enable upper bound use (e.g. value must be < or <= upper bound ) + /// @param upperBound - maximum value of the range + /// @param upperBoundExcluded - if true then the test is <, otherwise its <= + void enableUpperBound(uint32 upperBound, bool upperBoundExcluded); + + /// Enable lower bound use (e.g. value must be < or <= lower bound ) + /// @param lowerBound - minimum value of the range + /// @param lowerBoundExcluded - if true then the test is <, otherwise its <= + void enableLowerBound(uint32 lowerBound, bool lowerBoundExcluded); + + /// Disable upper bound usage + void disableUpperBound(void); + + /// Disable lower bound usage + void disableLowerBound(void); + + /// With changes wrapper to be called for the installation of new range values + void updateUi(); + +Q_SIGNALS: + void valueChanged(uint32 value); + +private Q_SLOTS: + void setMaximum(int value); + void setMinimum(int value); + void changeSlider(int value); + +private: + + IPSWrapperUInt *_Wrapper; + bool _emit; + Ui::CEditRangeUIntWidget _ui; +}; /* class CEditRangeUIntWidget */ + + +/** +@class CEditRangeIntWidget +@brief The widget provides a horizontal slider and 3 QSpinBox(to set start/end value range and сurrent value from this range.). +@details Slider sets the sint32 value within a specified range (start/end QSpinBox). +The values range can be set through the class methods: setRange() or setRangeMin(), setRangeMax(). +Or the user input values in the widgets start/end QSpinBox. +Also the range of start/end values can be restricted through the class methods: enableLowerBound(), enableUpperBound() +this widget can be used by a two ways: Qt Q_SIGNAL/SLOT or wrapper. + +1. Using the Qt Q_SIGNAL/SLOT current value can be set by class methods setValue() and +changes in current value(QSlider or QSpinBox) the signal valueChanged() will be emitted. + +2. Using wrapper, create wpapper struct, example: +@code +struct CRadialViscosityWrapper : public IPSWrapperFloat +{ + NL3D::CPSCylindricVortex *V; + sint32 get(void) const { return V->getRadialViscosity(); } + void set(const sint32 &value) { V->setRadialViscosity(value); } +} _RadialViscosityWrapper; +@endcode +to set the current values,it is need to call class methods updateUi(); +*/ + +class CEditRangeIntWidget: public QWidget +{ + Q_OBJECT + +public: + /// Constructor, sets 0 default current value + CEditRangeIntWidget(QWidget *parent = 0); + ~CEditRangeIntWidget(); + + /// set an interface of a wrapper to read / write values in the particle system + /// NB : The 'OwnerNode' field of the wrapper + void setWrapper(IPSWrapperInt *wrapper); + + /// Set current value + /// @param value - current value + /// @param emit - will emit valueChanged() if the new value is different from the old one and param emit = true + void setValue(sint32 value, bool emit = true); + + /// Convenience function to set the minimum, and maximum values with a single function call + void setRange(sint32 minValue, sint32 maxValue); + + /// Set the minimum value that can take range(slider) + void setRangeMin(sint32 minValue); + + /// Set the maximum value that can take range(slider) + void setRangeMax(sint32 maxValue); + + /// Enable upper bound use (e.g. value must be < or <= upper bound ) + /// @param upperBound - maximum value of the range + /// @param upperBoundExcluded - if true then the test is <, otherwise its <= + void enableUpperBound(sint32 upperBound, bool upperBoundExcluded); + + /// Enable lower bound use (e.g. value must be < or <= lower bound ) + /// @param lowerBound - minimum value of the range + /// @param lowerBoundExcluded - if true then the test is <, otherwise its <= + void enableLowerBound(sint32 lowerBound, bool lowerBoundExcluded); + + /// Disable upper bound usage + void disableUpperBound(void); + + /// Disable lower bound usage + void disableLowerBound(void); + + /// With changes wrapper to be called for the installation of new range values + void updateUi(); + +Q_SIGNALS: + void valueChanged(sint32 value); + +private Q_SLOTS: + void setMaximum(int value); + void setMinimum(int value); + void changeSlider(int value); + +private: + + IPSWrapperInt *_Wrapper; + bool _emit; + Ui::CEditRangeUIntWidget _ui; +}; /* class CEditRangeIntWidget */ + + +/** +@class CEditRangeFloatWidget +@brief The widget provides a horizontal slider and 3 QDoubleSpinBox(to set start/end value range and сurrent value from this range.). +@details Slider sets the float value within a specified range (start/end QDoubleSpinBox). +The values range can be set through the class methods: setRange() or setRangeMin(), setRangeMax(). +Or the user input values in the widgets start/end QDoubleSpinBox. +Also the range of start/end values can be restricted through the class methods: enableLowerBound(), enableUpperBound() +this widget can be used by a two ways: Qt Q_SIGNAL/SLOT or wrapper. + +1. Using the Qt Q_SIGNAL/SLOT current value can be set by class methods setValue() and +changes in current value(only QSlider) the signal valueChanged()will be emitted. + +2. Using wrapper, create wpapper struct, example: +@code +struct CTangentialViscosityWrapper : public IPSWrapperFloat +{ + NL3D::CPSCylindricVortex *V; + float get(void) const { return V->getTangentialViscosity(); } + void set(const float &value) { V->setTangentialViscosity(value); } +} _TangentialViscosityWrapper; +@endcode +to set the current values,it is need to call class methods updateUi(); +*/ + +class CEditRangeFloatWidget: public QWidget +{ + Q_OBJECT +public: + /// Constructor, sets 0 default current value + CEditRangeFloatWidget(QWidget *parent = 0); + ~CEditRangeFloatWidget(); + + /// Set an interface of a wrapper to read / write values in the particle system + /// NB : The 'OwnerNode' field of the wrapper + void setWrapper(IPSWrapperFloat *wrapper); + + /// Set current value + /// @param value - current value + /// @param emit - will emit valueChanged() if the new value is different from the old one and param emit = true + void setValue(float value, bool emit = true); + + /// Convenience function to set the minimum, and maximum values with a single function call + void setRange(float minValue, float maxValue); + + /// Set the minimum value that can take range(slider) + void setRangeMin(float minValue); + + /// Set the maximum value that can take range(slider) + void setRangeMax(float maxValue); + + /// Enable upper bound use (e.g. value must be < or <= upper bound ) + /// @param upperBound - maximum value of the range + /// @param upperBoundExcluded - if true then the test is <, otherwise its <= + void enableUpperBound(float upperBound, bool upperBoundExcluded); + + /// Enable lower bound use (e.g. value must be < or <= lower bound ) + /// @param lowerBound - minimum value of the range + /// @param lowerBoundExcluded - if true then the test is <, otherwise its <= + void enableLowerBound(float lowerBound, bool lowerBoundExcluded); + + /// Disable upper bound usage + void disableUpperBound(void); + + /// Disable lower bound usage + void disableLowerBound(void); + + /// With changes wrapper to be called for the installation of new range values + void updateUi(); + +Q_SIGNALS: + void valueChanged(float value); + +private Q_SLOTS: + void changeRange(); + void changeSlider(int value); + +private: + + IPSWrapperFloat *_Wrapper; + bool _emit; + Ui::CEditRangeFloatWidget _ui; +}; /* class CEditRangeFloatWidget */ + +} /* namespace NLQT */ + +#endif // EDIT_RANGE_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/emitter_form.ui b/code/nel/tools/3d/object_viewer_qt/src/emitter_form.ui new file mode 100644 index 000000000..ea5f11a8b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/emitter_form.ui @@ -0,0 +1,293 @@ + + + CEmitterPage + + + + 0 + 0 + 382 + 450 + + + + Form + + + + + + true + + + QTabWidget::East + + + 0 + + + true + + + + General + + + + + + + + Emitted type: + + + + + + + + + + Type of emission: + + + + + + + + Regular + + + + + On death + + + + + Once + + + + + On bounce + + + + + External only + + + + + + + + + + Direction: + + + + + + + + Default + + + + + Direction is aligned on the emitter direction + + + + + Direction is in world + + + + + Direction is local to the system + + + + + User + + + + + + + + Force consistent emission + + + + + + + Bypass Auto-LOD + + + + + + + Speed inheritance factor: + + + + + + + + + + Delayed emission: + + + + + + + + + + Max Emission Count: + + + + + + + + + + + + + Radius: + + + + + + + + + + Qt::Vertical + + + + 17 + 143 + + + + + + + + + Emission period + + + + + + Qt::Vertical + + + + 20 + 291 + + + + + + + + Emmision period + + + + + + + + Emmision quantity/Speed + + + + + + Emmision quantity + + + + + + + Strenght modulation + + + + + + + Qt::Vertical + + + + 20 + 275 + + + + + + + + + + + + + NLQT::CEditRangeUIntWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CDirectionWidget + QWidget +
direction_widget.h
+ 1 +
+ + NLQT::CAttribFloatWidget + QGroupBox +
attrib_widget.h
+ 1 +
+ + NLQT::CAttribUIntWidget + QGroupBox +
attrib_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/emitter_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/emitter_page.cpp new file mode 100644 index 000000000..c7e4da53f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/emitter_page.cpp @@ -0,0 +1,372 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "emitter_page.h" + +// Qt includes + #include + +// NeL includes + +// Project includes +#include "edit_range_widget.h" +#include "modules.h" + +namespace NLQT { + + +CEmitterPage::CEmitterPage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + // setup the dialog for the period of emission edition + _ui.periodWidget->setRange(0.f, 2.f); + _ui.periodWidget->setWrapper(&_PeriodWrapper); + _ui.periodWidget->setSchemeWrapper(&_PeriodWrapper); + _ui.periodWidget->init(); + + // setup the dialog that helps tuning the number of particle being emitted at a time + _ui.genNbWidget->setRange(1, 11); + _ui.genNbWidget->setWrapper(&_GenNbWrapper); + _ui.genNbWidget->setSchemeWrapper(&_GenNbWrapper); + _ui.genNbWidget->init(); + + // deals with emitters that have a direction + _ui.strenghtModulateWidget->setRange(0, 10); + _ui.strenghtModulateWidget->setWrapper(&_ModulatedStrenghtWrapper); + _ui.strenghtModulateWidget->setSchemeWrapper(&_ModulatedStrenghtWrapper); + _ui.strenghtModulateWidget->init(); + + // SPEED_INHERITANCE_FACTOR + _ui.speedInherFactorWidget->setRange(-1.f, 1.f); + _ui.speedInherFactorWidget->setWrapper(&_SpeedInheritanceFactorWrapper); + + // DELAYED_EMISSION + _ui.delayedEmissionWidget->setRange(0.f, 10.f); + _ui.delayedEmissionWidget->enableLowerBound(0.f, false); + _ui.delayedEmissionWidget->setWrapper(&_DelayedEmissionWrapper); + + // MAX_EMISSION_COUNT + _ui.maxEmissionCountWidget->setRange(0, 100); + _ui.maxEmissionCountWidget->enableUpperBound(256, false); + _ui.maxEmissionCountWidget->setWrapper(&_MaxEmissionCountWrapper); + _MaxEmissionCountWrapper.widget = _ui.maxEmissionCountWidget; + _MaxEmissionCountWrapper.parent = this; + + // radius for conic emitter + _ui.radiusWidget->setRange(0.1f, 2.1f); + _ui.radiusWidget->setWrapper(&_ConicEmitterRadiusWrapper); + + _ui.directionWidget->setWrapper(&_DirectionWrapper); + + connect(_ui.emittedTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setEmittedType(int))); + connect(_ui.typeEmissionComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setTypeOfEmission(int))); + connect(_ui.directionComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setDirectionMode(int))); + connect(_ui.bypassAutoLODCheckBox, SIGNAL(toggled(bool)), this, SLOT(setBypassAutoLOD(bool))); + connect(_ui.forceConsistentCheckBox, SIGNAL(toggled(bool)), this, SLOT(setConsistentEmission(bool))); +} + +CEmitterPage::~CEmitterPage() +{ +} + +void CEmitterPage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable) +{ + _Emitter = static_cast(locatedBindable); + _Node = ownerNode; + + updateEmittedType(); + + _SpeedInheritanceFactorWrapper.OwnerNode = _Node; + _SpeedInheritanceFactorWrapper.E = _Emitter; + _ui.speedInherFactorWidget->updateUi(); + + _DelayedEmissionWrapper.Node = _Node; + _DelayedEmissionWrapper.OwnerNode = _Node; + _DelayedEmissionWrapper.E = _Emitter; + _ui.delayedEmissionWidget->updateUi(); + + _MaxEmissionCountWrapper.Node = _Node; + _MaxEmissionCountWrapper.OwnerNode = _Node; + _MaxEmissionCountWrapper.E = _Emitter; + _ui.maxEmissionCountWidget->updateUi(); + + _PeriodWrapper.Node = _Node; + _PeriodWrapper.E = _Emitter; + _ui.periodWidget->setWorkspaceNode(_Node); + _ui.periodWidget->updateUi(); + + _GenNbWrapper.Node = _Node; + _GenNbWrapper.E = _Emitter; + _ui.genNbWidget->setWorkspaceNode(_Node); + _ui.genNbWidget->updateUi(); + + if (dynamic_cast(_Emitter)) + { + _ModulatedStrenghtWrapper.E = dynamic_cast(_Emitter); + _ui.strenghtModulateWidget->setWorkspaceNode(_Node); + _ui.strenghtModulateWidget->updateUi(); + _ui.strenghtModulateWidget->show(); + } + else + _ui.strenghtModulateWidget->hide(); + + + // deals with emitters that have a direction + if (dynamic_cast(_Emitter)) + { + _DirectionWrapper.E = dynamic_cast(_Emitter); + _ui.directionWidget->setDirectionWrapper(dynamic_cast(_Emitter)); + _ui.directionWidget->updateUi(); + _ui.directionWidget->show(); + } + else + _ui.directionWidget->hide(); + + + // radius for conic emitter + if (dynamic_cast(_Emitter)) + { + _ConicEmitterRadiusWrapper.E = dynamic_cast(_Emitter); + _ui.radiusWidget->updateUi(); + _ui.radiusWidget->show(); + _ui.radiusLabel->show(); + } + else + { + _ui.radiusWidget->hide(); + _ui.radiusLabel->hide(); + } + + _ui.directionComboBox->blockSignals(true); + if (_Emitter->isSpeedBasisEmissionEnabled()) + { + _ui.directionComboBox->setCurrentIndex(int(AlignOnEmitterDirection)); + } + else if (!_Emitter->isUserMatrixModeForEmissionDirectionEnabled()) + { + _ui.directionComboBox->setCurrentIndex(int(Default)); + } + else if (_Emitter->getUserMatrixModeForEmissionDirection() == NL3D::PSFXWorldMatrix) + { + _ui.directionComboBox->setCurrentIndex(int(LocalToSystem)); + } + else if (_Emitter->getUserMatrixModeForEmissionDirection() == NL3D::PSIdentityMatrix) + { + _ui.directionComboBox->setCurrentIndex(int(InWorld)); + } + else if (_Emitter->getUserMatrixModeForEmissionDirection() == NL3D::PSUserMatrix) + { + _ui.directionComboBox->setCurrentIndex(int(LocalToFatherSkeleton)); + } + else + { + nlassert(0); + } + _ui.directionComboBox->blockSignals(false); + + updatePeriodWidget(); + + _ui.typeEmissionComboBox->setCurrentIndex(int(_Emitter->getEmissionType())); + + // bypass auto LOD + nlassert(_Emitter->getOwner() && _Emitter->getOwner()->getOwner()); + NL3D::CParticleSystem &ps = *_Emitter->getOwner()->getOwner(); + if (ps.isAutoLODEnabled() && !ps.isSharingEnabled()) + { + _ui.bypassAutoLODCheckBox->setEnabled(true); + _ui.bypassAutoLODCheckBox->setChecked(_Emitter->getBypassAutoLOD()); + } + else + _ui.bypassAutoLODCheckBox->setEnabled(true); +} + +void CEmitterPage::setEmittedType(int index) +{ + if (!_Emitter->setEmittedType(_LocatedList[index])) + { + if (_Emitter->getOwner()->getOwner()->getBehaviourType() == NL3D::CParticleSystem::SpellFX || _Emitter->getOwner()->getOwner()->getBypassMaxNumIntegrationSteps()) + { + QMessageBox::critical(this, tr("NeL Particle Editor"), + tr("Can't perform operation : the system is flagged with 'No max nb steps' or uses the preset 'Spell FX'," + "and thus, should have a finite duration. This operation create a loop in the system, and so is forbidden."), + QMessageBox::Ok); + } + else + { + QMessageBox::critical(this, tr("NeL Particle Editor"), + tr("Loops with emitters are forbidden."), + QMessageBox::Ok); + } + updateEmittedType(); + } + + Modules::psEdit().resetAutoCount(_Node); + updateModifiedFlag(); +} + +void CEmitterPage::setTypeOfEmission(int index) +{ + if (_Emitter->getEmissionType() == index) return; + if (!_Emitter->setEmissionType((NL3D::CPSEmitter::TEmissionType) index)) + { + QMessageBox::critical(this, tr("NeL Particle Editor"), + tr("Can't perform operation : the system is flagged with 'No max nb steps' or uses the preset 'Spell FX', " + "and thus, should have a finite duration. Please remove that flag first."), + QMessageBox::Ok); + + _ui.typeEmissionComboBox->setCurrentIndex(int(_Emitter->getEmissionType())); + } + + updatePeriodWidget(); + + Modules::psEdit().resetAutoCount(_Node); + + updateModifiedFlag(); +} + +void CEmitterPage::setConsistentEmission(bool state) +{ + if (_Emitter-> isConsistentEmissionEnabled() == state) return; + _Emitter->enableConsistenEmission(state); + updateModifiedFlag(); +} + +void CEmitterPage::setBypassAutoLOD(bool state) +{ + if (_Emitter->getBypassAutoLOD() == state) return; + _Emitter->setBypassAutoLOD(state); + updateModifiedFlag(); +} + +void CEmitterPage::setDirectionMode(int index) +{ + nlassert(_Emitter); + switch(index) + { + case Default: + _Emitter->enableSpeedBasisEmission(false); + _Emitter->enableUserMatrixModeForEmissionDirection(false); + break; + case AlignOnEmitterDirection: + _Emitter->enableSpeedBasisEmission(true); + _Emitter->enableUserMatrixModeForEmissionDirection(false); + break; + case InWorld: + _Emitter->enableSpeedBasisEmission(false); + _Emitter->enableUserMatrixModeForEmissionDirection(true); + _Emitter->setUserMatrixModeForEmissionDirection(NL3D::PSIdentityMatrix); + break; + case LocalToSystem: + _Emitter->enableSpeedBasisEmission(false); + _Emitter->enableUserMatrixModeForEmissionDirection(true); + _Emitter->setUserMatrixModeForEmissionDirection(NL3D::PSFXWorldMatrix); + break; + case LocalToFatherSkeleton: + _Emitter->enableSpeedBasisEmission(false); + _Emitter->enableUserMatrixModeForEmissionDirection(true); + _Emitter->setUserMatrixModeForEmissionDirection(NL3D::PSUserMatrix); + break; + } + updateModifiedFlag(); +} + +void CEmitterPage::updatePeriodWidget() +{ + bool bEnable = _Emitter->getEmissionType() == NL3D::CPSEmitter::regular; + + _ui.periodWidget->setEnabled(bEnable); + _ui.delayedEmissionWidget->setEnabled(bEnable); + _ui.delayedEmissionLabel->setEnabled(bEnable); + _ui.maxEmissionCountWidget->setEnabled(bEnable); + _ui.maxEmissinCountLabel->setEnabled(bEnable); +} + +void CEmitterPage::updateEmittedType() +{ + disconnect(_ui.emittedTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setEmittedType(int))); + + _ui.emittedTypeComboBox->clear(); + _ui.emittedTypeComboBox->addItem(tr("no emission")); + _LocatedList.clear(); + _LocatedList.push_back(NULL); + NL3D::CParticleSystem *ps = _Emitter->getOwner()->getOwner(); + uint nbLocated = ps->getNbProcess(); + for (uint k = 0; k < nbLocated; ++k) + { + NL3D::CPSLocated *loc = dynamic_cast(ps->getProcess(k)); + if (loc) // is this a located + { + _ui.emittedTypeComboBox->addItem(QString(loc->getName().c_str())); + _LocatedList.push_back(loc); + if (loc == _Emitter->getEmittedType()) + _ui.emittedTypeComboBox->setCurrentIndex(k + 1); + } + } + connect(_ui.emittedTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setEmittedType(int))); +} + +void CEmitterPage::CPeriodWrapper::set(const float &v) +{ + E->setPeriod(v); + Modules::psEdit().resetAutoCount(Node); +} +void CEmitterPage::CPeriodWrapper::setScheme(scheme_type *s) +{ + E->setPeriodScheme(s); + Modules::psEdit().resetAutoCount(Node); +} + +void CEmitterPage::CGenNbWrapper::set(const uint32 &v) +{ + E->setGenNb(v); + Modules::psEdit().resetAutoCount(Node); +} + +void CEmitterPage::CGenNbWrapper::setScheme(scheme_type *s) +{ + E->setGenNbScheme(s); + Modules::psEdit().resetAutoCount(Node); +} + + +void CEmitterPage::CDelayedEmissionWrapper::set(const float &f) +{ + E->setEmitDelay(f); + Modules::psEdit().resetAutoCount(Node); +} + +void CEmitterPage::CMaxEmissionCountWrapper::set(const uint32 &count) +{ + if (!E->setMaxEmissionCount((uint8) count)) + { + + QMessageBox::critical(parent, tr("NeL Particle Editor"), + tr("Can't perform operation : the system is flagged with 'No max nb steps' or uses the preset 'Spell FX', " + "and thus, should have a finite duration. Please remove that flag first."), + QMessageBox::Ok); + + widget->updateUi(); + } + Modules::psEdit().resetAutoCount(Node); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/emitter_page.h b/code/nel/tools/3d/object_viewer_qt/src/emitter_page.h new file mode 100644 index 000000000..855eec324 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/emitter_page.h @@ -0,0 +1,168 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef EMITTER_PAGE_H +#define EMITTER_PAGE_H + +#include +#include "ui_emitter_form.h" + +// STL includes + +// NeL includes +#include "nel/3d/ps_particle.h" +#include "nel/3d/ps_emitter.h" + +// Project includes +#include "particle_node.h" +#include "ps_wrapper.h" + +namespace NLQT { + +/** +@class CEmitterPage +@brief Page for QStackWidget, to edit emitters in a particle system +*/ +class CEmitterPage: public QWidget +{ + Q_OBJECT + +public: + /// This enum match the option in the combo box that allow to choose how the direction of emission is computed. + enum TDirectionMode + { + Default = 0, + AlignOnEmitterDirection, + InWorld, + LocalToSystem, + LocalToFatherSkeleton + }; + + CEmitterPage(QWidget *parent = 0); + ~CEmitterPage(); + + /// Set the emitter to edit. + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable); + +private Q_SLOTS: + void setEmittedType(int index); + void setTypeOfEmission(int index); + void setConsistentEmission(bool state); + void setBypassAutoLOD(bool state); + void setDirectionMode(int index); + +private: + + /// period of emission + struct CPeriodWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + CWorkspaceNode *Node; + NL3D::CPSEmitter *E; + float get(void) const { return E->getPeriod(); } + void set(const float &v); + scheme_type *getScheme(void) const { return E->getPeriodScheme(); } + void setScheme(scheme_type *s); + } _PeriodWrapper; + + /// number of particle to generate each time + struct CGenNbWrapper : public IPSWrapperUInt, IPSSchemeWrapperUInt + { + CWorkspaceNode *Node; + NL3D::CPSEmitter *E; + uint32 get(void) const { return E->getGenNb(); } + void set(const uint32 &v); + scheme_type *getScheme(void) const { return E->getGenNbScheme(); } + void setScheme(scheme_type *s); + } _GenNbWrapper; + + /// wrappers to emitters that have strenght modulation + struct CModulateStrenghtWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSModulatedEmitter *E; + float get(void) const { return E->getEmitteeSpeed(); } + void set(const float &v) { E->setEmitteeSpeed(v); } + scheme_type *getScheme(void) const { return E->getEmitteeSpeedScheme(); } + void setScheme(scheme_type *s) { E->setEmitteeSpeedScheme(s); } + } _ModulatedStrenghtWrapper; + + /// wrappers to set the speed inheritance factor + struct CSpeedInheritanceFactorWrapper : public IPSWrapperFloat + { + NL3D::CPSEmitter *E; + float get(void) const { return E->getSpeedInheritanceFactor(); } + void set(const float &f) { E->setSpeedInheritanceFactor(f); } + } _SpeedInheritanceFactorWrapper; + + /// wrappers to tune the direction of emitters + struct CDirectionWrapper : public IPSWrapper + { + NL3D::CPSDirection *E; + NLMISC::CVector get(void) const { return E->getDir(); } + void set(const NLMISC::CVector &d){ E->setDir(d); } + } _DirectionWrapper; + + /// wrapper to tune the radius of an emitter + struct CConicEmitterRadiusWrapper : public IPSWrapperFloat + { + NL3D::CPSEmitterConic *E; + float get(void) const { return E->getRadius(); } + void set(const float &f) { E->setRadius(f); } + } _ConicEmitterRadiusWrapper; + + /// wrapper to tune delayed emission + struct CDelayedEmissionWrapper : public IPSWrapperFloat + { + CWorkspaceNode *Node; + NL3D::CPSEmitter *E; + float get(void) const { return E->getEmitDelay(); } + void set(const float &f); + } _DelayedEmissionWrapper; + + /// wrapper to tune max number of emissions + struct CMaxEmissionCountWrapper : public IPSWrapperUInt + { + CWorkspaceNode *Node; + CEditRangeUIntWidget *widget; + QWidget *parent; + NL3D::CPSEmitter *E; + uint32 get(void) const { return E->getMaxEmissionCount(); } + void set(const uint32 &count); + } _MaxEmissionCountWrapper; + + + // the emitter being edited + NL3D::CPSEmitter *_Emitter; + + // contains pointers to the located + std::vector _LocatedList; + + void updateEmittedType(); + + void updatePeriodWidget(); + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + CWorkspaceNode *_Node; + + Ui::CEmitterPage _ui; +}; /* class CEmitterPage */ + +} /* namespace NLQT */ + +#endif // EMITTER_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/entity.cpp b/code/nel/tools/3d/object_viewer_qt/src/entity.cpp new file mode 100644 index 000000000..304ff62bf --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/entity.cpp @@ -0,0 +1,430 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "entity.h" + +#include + +// NeL includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Project includes +#include "modules.h" + +using namespace NLMISC; +using namespace NL3D; + +namespace NLQT { + +CSlotInfo& CSlotInfo::operator=(const CSlotInfo & slotInfo) +{ + if ( this != &slotInfo) + { + Animation = slotInfo.Animation; + ClampMode = slotInfo.ClampMode; + Enable = slotInfo.Enable; + EndBlend = slotInfo.EndBlend; + EndTime = slotInfo.EndTime; + Offset = slotInfo.Offset; + Skeleton = slotInfo.Skeleton; + SkeletonInverted = slotInfo.SkeletonInverted; + Smoothness = slotInfo.Smoothness; + SpeedFactor = slotInfo.SpeedFactor; + StartBlend = slotInfo.StartBlend; + StartTime = slotInfo.StartTime; + } + return *this; +} + +CEntity::CEntity(void): + _Name(""), _FileNameShape(""), + _FileNameSkeleton(""), _inPlace(false), _incPos(false), + _Instance(NULL), _Skeleton(NULL), + _PlayList(NULL), _AnimationSet(NULL) +{ + _CharacterScalePos = 1; +} + +CEntity::~CEntity(void) +{ + if (_PlayList != NULL) + { + _PlayList->resetAllChannels(); + Modules::objView().getPlayListManager()->deletePlayList (_PlayList); + _PlayList = NULL; + } + if (_AnimationSet != NULL) + { + Modules::objView().getDriver()->deleteAnimationSet(_AnimationSet); + _AnimationSet = NULL; + } + if (!_Skeleton.empty()) + { + _Skeleton.detachSkeletonSon(_Instance); + Modules::objView().getScene()->deleteSkeleton(_Skeleton); + _Skeleton = NULL; + } + if (!_Instance.empty()) + { + Modules::objView().getScene()->deleteInstance(_Instance); + _Instance = NULL; + } +} + +void CEntity::loadAnimation(std::string &fileName) +{ + uint id = _AnimationSet->addAnimation(fileName.c_str(),CFile::getFilenameWithoutExtension(fileName).c_str()); + _AnimationList.push_back(_AnimationSet->getAnimationName(id)); + _AnimationSet->build(); + if (!_Skeleton.empty()) _PlayList->registerTransform(_Skeleton); + else _PlayList->registerTransform(_Instance); +} + +void CEntity::loadSWT(std::string &fileName) +{ + uint id = _AnimationSet->addSkeletonWeight(fileName.c_str(),CFile::getFilenameWithoutExtension(fileName).c_str()); + _SWTList.push_back(_AnimationSet->getSkeletonWeightName(id)); +} + +void CEntity::addAnimToPlayList(std::string &name) +{ + _PlayListAnimation.push_back(name); + + _AnimationStatus.EndAnim = this->getPlayListLength(); + + _Instance.start(); +} + +void CEntity::removeAnimToPlayList(uint row) +{ + if (row < _PlayListAnimation.size()) + _PlayListAnimation.erase(_PlayListAnimation.begin() + row); + + _AnimationStatus.EndAnim = this->getPlayListLength(); +} + +void CEntity::swapAnimToPlayList(uint row1, uint row2) +{ + if ((row1 < _PlayListAnimation.size()) && (row2 < _PlayListAnimation.size())) + std::swap(_PlayListAnimation[row1], _PlayListAnimation[row2]); +} + +void CEntity::playbackAnim(bool play) +{ + _AnimationStatus.PlayAnim = play; + + if (play) + _Instance.start(); + else + _Instance.freezeHRC(); +} + +void CEntity::reset() +{ + _PlayListAnimation.clear(); + _AnimationList.clear(); + _SWTList.clear(); + + _PlayList->resetAllChannels(); +} + +float CEntity::getPlayListLength() +{ + // Accumul all the time + float time = 0; + for(size_t i = 0; i < _PlayListAnimation.size(); ++i) + time += getAnimLength(_PlayListAnimation[i]); + return time; +} + +float CEntity::getAnimLength(std::string name) +{ + uint id = _AnimationSet->getAnimationIdByName(name.c_str()); + NL3D::UAnimation *anim = _AnimationSet->getAnimation(id); + return anim->getEndTime() - anim->getBeginTime(); +} + +void CEntity::update(NL3D::TAnimationTime time) +{ + this->resetChannel(); + switch (_AnimationStatus.Mode) + { + case Mode::PlayList: + animatePlayList(time); + break; + case Mode::Mixer: + animateChannelMixer(); + break; + } +} + + +void CEntity::resetChannel() +{ + for(uint i = 0; i < NL3D::CChannelMixer::NumAnimationSlot; i++) + _PlayList->setAnimation(i, UPlayList::empty); +} + +void CEntity::addTransformation (CMatrix ¤t, UAnimation *anim, float begin, float end, UTrack *posTrack, UTrack *rotquatTrack, + UTrack *nextPosTrack, UTrack *nextRotquatTrack, bool removeLast) +{ + // In place ? + if (_inPlace) + { + // Just identity + current.identity(); + } + else + { + // Remove the start of the animation + CQuat rotEnd (0,0,0,1); + CVector posEnd (0,0,0); + if (rotquatTrack) + { + // Interpolate the rotation + rotquatTrack->interpolate (end, rotEnd); + } + if (posTrack) + { + // Interpolate the position + posTrack->interpolate (end, posEnd); + } + + // Add the final rotation and position + CMatrix tmp; + tmp.identity (); + tmp.setRot (rotEnd); + tmp.setPos (posEnd); + + // Incremental ? + if (_incPos) + current *= tmp; + else + current = tmp; + + if (removeLast) + { + CQuat rotStart (0,0,0,1); + CVector posStart (0,0,0); + if (nextRotquatTrack) + { + // Interpolate the rotation + nextRotquatTrack->interpolate (begin, rotStart); + } + if (nextPosTrack) + { + // Interpolate the position + nextPosTrack->interpolate (begin, posStart); + } + // Remove the init rotation and position of the next animation + tmp.identity (); + tmp.setRot (rotStart); + tmp.setPos (posStart); + tmp.invert (); + current *= tmp; + + // Normalize the mt + CVector I = current.getI (); + CVector J = current.getJ (); + I.z = 0; + J.z = 0; + J.normalize (); + CVector K = I^J; + K.normalize (); + I = J^K; + I.normalize (); + tmp.setRot (I, J, K); + tmp.setPos (current.getPos ()); + current = tmp; + } + } +} + +void CEntity::animatePlayList(NL3D::TAnimationTime time) +{ + if (!_PlayListAnimation.empty()) + { + // Animation index + uint id = _AnimationSet->getAnimationIdByName(_PlayListAnimation[0].c_str()); + + // Try channel AnimationSet + NL3D::UAnimation *anim = _AnimationSet->getAnimation(id); + + bool there = false; + + UTrack *posTrack = NULL; + UTrack *rotQuatTrack = NULL; + + // Current matrix + CMatrix current; + current.identity(); + + // read an animation for init matrix + rotQuatTrack = anim->getTrackByName("rotquat"); + posTrack = anim->getTrackByName("pos"); + + there = posTrack || rotQuatTrack; + + // Accumul time + float startTime = 0; + float endTime = anim->getEndTime() - anim->getBeginTime(); + + uint index = 0; + while (time >= endTime) + { + index++; + if (index < _PlayListAnimation.size()) + { + id = _AnimationSet->getAnimationIdByName(_PlayListAnimation[index].c_str()); + NL3D::UAnimation *newAnim = _AnimationSet->getAnimation(id); + + UTrack *newPosTrack = newAnim->getTrackByName ("pos"); + UTrack *newRotquatTrack = newAnim->getTrackByName ("rotquat"); + + // Add the transformation + addTransformation (current, anim, newAnim->getBeginTime(), anim->getEndTime(), posTrack, rotQuatTrack, newPosTrack, newRotquatTrack, true); + + + anim = newAnim; + posTrack = newPosTrack; + rotQuatTrack = newRotquatTrack; + + // Add start time + startTime = endTime; + endTime = startTime + (anim->getEndTime() - anim->getBeginTime()); + } + else + { + // Add the transformation + addTransformation (current, anim, 0, anim->getEndTime(), posTrack, rotQuatTrack, NULL, NULL, false); + break; + } + } + + // Time cropped ? + if (index >= _PlayListAnimation.size()) + { + // Yes + index--; + id = _AnimationSet->getAnimationIdByName(_PlayListAnimation[index].c_str()); + anim = _AnimationSet->getAnimation(id); + + // End time for last anim + startTime = anim->getEndTime() - time; + } + else + { + // No + + // Add the transformation + addTransformation (current, anim, 0, anim->getBeginTime() + time - startTime, posTrack, rotQuatTrack, NULL, NULL, false); + + id = _AnimationSet->getAnimationIdByName(_PlayListAnimation[index].c_str()); + anim = _AnimationSet->getAnimation(id); + + // Final time + startTime -= anim->getBeginTime(); + } + + // Set the slot + _PlayList->setAnimation(0, id); + _PlayList->setTimeOrigin(0, startTime); + _PlayList->setSpeedFactor(0, 1.0f); + _PlayList->setWeightSmoothness(0, 1.0f); + _PlayList->setStartWeight(0, 1, 0); + _PlayList->setEndWeight(0, 1, 1); + _PlayList->setWrapMode(0, UPlayList::Clamp); + + // Setup the pos and rot for this shape + if (there) + { + CVector pos = current.getPos(); + + // If a skeleton model + if(!_Skeleton.empty()) + { + // scale animated pos value with the CFG scale + pos *= _CharacterScalePos; + _Skeleton.setPos(pos); + _Skeleton.setRotQuat(current.getRot()); + } + else + { + _Instance.setPos(pos); + _Instance.setRotQuat(current.getRot()); + } + } + } +} + +void CEntity::animateChannelMixer() +{ + for (uint i = 0; i < NL3D::CChannelMixer::NumAnimationSlot; i++) + { + if (_SlotInfo[i].Enable) + { + // Set the animation + uint animId = _AnimationSet->getAnimationIdByName(_SlotInfo[i].Animation); + if (animId == UAnimationSet::NotFound) + _PlayList->setAnimation(i, UPlayList::empty); + else + _PlayList->setAnimation(i, animId); + + // Set the skeleton weight + uint skelId = _AnimationSet->getSkeletonWeightIdByName(_SlotInfo[i].Skeleton); + if (skelId == UAnimationSet::NotFound) + _PlayList->setSkeletonWeight(i, UPlayList::empty, false); + else + _PlayList->setSkeletonWeight(i, skelId, _SlotInfo[i].SkeletonInverted); + + // Set others values + _PlayList->setTimeOrigin(i, _SlotInfo[i].Offset); + _PlayList->setSpeedFactor(i, _SlotInfo[i].SpeedFactor); + _PlayList->setStartWeight(i, _SlotInfo[i].StartBlend, _SlotInfo[i].StartTime); + _PlayList->setEndWeight(i, _SlotInfo[i].EndBlend, _SlotInfo[i].EndTime); + _PlayList->setWeightSmoothness(i, _SlotInfo[i].Smoothness); + + // Switch between wrap modes + switch (_SlotInfo[i].ClampMode) + { + case 0: + _PlayList->setWrapMode (i, UPlayList::Clamp); + break; + case 1: + _PlayList->setWrapMode (i, UPlayList::Repeat); + break; + case 2: + _PlayList->setWrapMode (i, UPlayList::Disable); + break; + } + } + } +} + +} /* namespace NLQT */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/entity.h b/code/nel/tools/3d/object_viewer_qt/src/entity.h new file mode 100644 index 000000000..f37995372 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/entity.h @@ -0,0 +1,259 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef ENTITY_H +#define ENTITY_H + +#include + +// STL includes +#include +#include +#include + +// NeL includes +#include +#include +#include +#include +#include +#include +#include + +namespace NL3D { + class UPlayList; + class UAnimationSet; +} + +namespace NLQT { + +class CSlotInfo +{ +public: + CSlotInfo (): + Animation("empty"), Skeleton("empty"), + Offset(0), StartTime(0), EndTime(0), + StartBlend(1), EndBlend (1), Smoothness(1), + SpeedFactor(1), ClampMode(0), + SkeletonInverted(false), + Enable(true) {} ; + + std::string Animation; + std::string Skeleton; + float Offset; + float StartTime; + float EndTime; + + float StartBlend; + float EndBlend; + float Smoothness; + float SpeedFactor; + sint32 ClampMode; + bool SkeletonInverted; + bool Enable; + + CSlotInfo &operator=(const CSlotInfo &); +}; + + +/** +@class CEntity +@brief Class manage animated shape. +@details +Allows you to load animations for shape and skeleton weight. +Contains a built-in playlist. Has management and playback Playlists or Mixer. +*/ +class CEntity +{ +public: + struct Mode + { + enum List + { + PlayList = 1, + Mixer + }; + }; + + /// Will need for a single or multiple animation shape + struct SAnimationStatus + { + bool LoopAnim; + bool PlayAnim; + float CurrentTimeAnim; + float StartAnim; + float EndAnim; + float SpeedAnim; + int Mode; + + SAnimationStatus(): + LoopAnim(false), PlayAnim(false), + CurrentTimeAnim(0), StartAnim(0), + EndAnim(0), SpeedAnim(1), Mode(Mode::PlayList) {} + }; + + /// Destructor + ~CEntity(void); + + /// Loads a file animations + /// @param fileName - name animation file + void loadAnimation(std::string &fileName); + + /// Loads a file skeleton weight + void loadSWT(std::string &fileName); + + /// Adds an animation to a playlist + /// @param name - name loaded animations + void addAnimToPlayList(std::string &name); + + /// Removes the animation from a playlist + /// @param row - number of animations in the playlist + void removeAnimToPlayList(uint row); + + /// Swaps animations to a playlist + /// @param row1 - first number of animations in the playlist + /// @param row2 - second number of animations in the playlist + void swapAnimToPlayList(uint row1, uint row2); + + /// Playback animation + void playbackAnim(bool play); + + /// Reset playlist and animation + void reset(); + + /// Get the total time of animation playlist + /// @return total time of animation + float getPlayListLength(); + + /// get time length single animation + float getAnimLength(std::string name); + + /// Get slot infomation + void setSlotInfo(uint num, CSlotInfo& slotInfo) { _SlotInfo[num] = slotInfo; } + + /// Set use mode playlist or mixer + void setMode(int mode) { _AnimationStatus.Mode = mode; } + + /// Set in place mode animation + void setInPlace(bool enabled) { _inPlace = enabled; } + + /// Get in place mode + bool getInPlace() { return _inPlace; } + + /// Set inc position + void setIncPos(bool enabled) { _incPos = enabled; } + + /// Get inc position + bool getIncPos() { return _incPos; } + + /// Get information about the current status of playing a playlist + /// @return struct containing current information playback + SAnimationStatus getStatus() { return _AnimationStatus; } + + /// Get name entity + /// @return name entity + std::string getName() { return _Name; } + + /// Get file name shape + /// @return file name shape + std::string getFileNameShape() { return _FileNameShape; } + + /// Get file name skeleton + /// @return file name skeleton + std::string getFileNameSkeleton() { return _FileNameSkeleton; } + + /// Get slot information + CSlotInfo getSlotInfo(uint num) { return _SlotInfo[num]; } + + /// Get list loaded animations files + std::vector& getAnimationList() { return _AnimationList; } + + /// Get playlist animations + std::vector& getPlayListAnimation() { return _PlayListAnimation; } + + /// Get list loaded skeleton weight template files + std::vector& getSWTList() { return _SWTList; } + + /// Get game interface for manipulating Skeleton. + NL3D::USkeleton getSkeleton() const { return _Skeleton; } + +private: + /// Constructor + CEntity(void); + + /// Update the animate from the playlist or channel mixer + /// @param time - current time in second + void update(NL3D::TAnimationTime time); + + void resetChannel(); + + /// Update the animate from the playlist + void animatePlayList(NL3D::TAnimationTime time); + + /// Update the animate from the mixer + void animateChannelMixer(); + void addTransformation (NLMISC::CMatrix ¤t, NL3D::UAnimation *anim, + float begin, float end, + NL3D::UTrack *posTrack, NL3D::UTrack *rotquatTrack, + NL3D::UTrack *nextPosTrack, NL3D::UTrack *nextRotquatTrack, + bool removeLast); + + // The name of the entity + std::string _Name; + std::string _FileNameShape; + std::string _FileNameSkeleton; + + SAnimationStatus _AnimationStatus; + + bool _inPlace; + bool _incPos; + + float _CharacterScalePos; + + // The mesh instance associated to this entity + NL3D::UInstance _Instance; + + // The skeleton binded to the instance + NL3D::USkeleton _Skeleton; + + NL3D::UPlayList *_PlayList; + + NL3D::UAnimationSet *_AnimationSet; + + // Animation input file + std::vector _AnimationList; + + // Skeleton weight input file + std::vector _SWTList; + + // Play list animation + std::vector _PlayListAnimation; + + // Slot info for this object + CSlotInfo _SlotInfo[NL3D::CChannelMixer::NumAnimationSlot]; + + friend class CObjectViewer; +}; /* class CEntity */ + +typedef std::map CEntities; +typedef CEntities::iterator EIT; + +} /* namespace NLQT */ + +#endif // ENTITY_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.cpp new file mode 100644 index 000000000..3ded7adb7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.cpp @@ -0,0 +1,81 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "follow_path_dialog.h" + +// NeL include +#include + +// Project includes +#include "particle_node.h" + +namespace NLQT { + +CFollowPathDialog::CFollowPathDialog(NL3D::CPSPlaneBasisFollowSpeed *pbfs, CWorkspaceNode *ownerNode, QWidget *parent) + : QDialog(parent), _FollowPath(pbfs), _Node(ownerNode) +{ + resize(270, 90); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); + setSizePolicy(sizePolicy); + setMinimumSize(QSize(0, 90)); + setMaximumSize(QSize(16777215, 90)); + gridLayout = new QGridLayout(this); + label = new QLabel(this); + gridLayout->addWidget(label, 0, 0, 1, 1); + comboBox = new QComboBox(this); + gridLayout->addWidget(comboBox, 1, 0, 1, 2); + horizontalSpacer = new QSpacerItem(207, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + gridLayout->addItem(horizontalSpacer, 2, 0, 1, 1); + pushButton = new QPushButton(this); + gridLayout->addWidget(pushButton, 2, 1, 1, 1); + + setWindowTitle(tr("Follow path param")); + label->setText(tr("Projection plane:")); + comboBox->clear(); + comboBox->insertItems(0, QStringList() + << tr("No projection") + << tr("XY plane") + << tr("XZ plane") + << tr("YZ plane")); + pushButton->setText(("Ok")); + + comboBox->setCurrentIndex(_FollowPath->getProjectionPlane()); + + setFixedHeight(sizeHint().height()); + + connect(pushButton, SIGNAL(clicked()), this, SLOT(accept())); + connect(comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setProjectionMode(int))); +} + +CFollowPathDialog::~CFollowPathDialog() +{ +} + +void CFollowPathDialog::setProjectionMode(int index) +{ + nlassert(_FollowPath); + _FollowPath->setProjectionPlane((NL3D::CPSPlaneBasisFollowSpeed::TProjectionPlane) index); + if (_Node) _Node->setModified(true); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.h new file mode 100644 index 000000000..f9834c5d6 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/follow_path_dialog.h @@ -0,0 +1,63 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef FOLLOW_PATH_DIALOG_H +#define FOLLOW_PATH_DIALOG_H + +#include +#include +#include +#include +#include +#include + +namespace NL3D +{ + class CPSPlaneBasisFollowSpeed; +} + +namespace NLQT { + +class CWorkspaceNode; + +class CFollowPathDialog : public QDialog +{ + Q_OBJECT + +public: + CFollowPathDialog(NL3D::CPSPlaneBasisFollowSpeed *pbfs, CWorkspaceNode *ownerNode, QWidget *parent = 0); + ~CFollowPathDialog(); + +private Q_SLOTS: + void setProjectionMode(int index); + +protected: + QGridLayout *gridLayout; + QLabel *label; + QComboBox *comboBox; + QSpacerItem *horizontalSpacer; + QPushButton *pushButton; + + NL3D::CPSPlaneBasisFollowSpeed *_FollowPath; + CWorkspaceNode *_Node; +}; /* class CFollowPathDialog */ + +} /* namespace NLQT */ + +#endif // FOLLOW_PATH_DIALOG_H \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.cpp new file mode 100644 index 000000000..764aa6329 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.cpp @@ -0,0 +1,70 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "global_wind_dialog.h" + +// Project includes +#include "modules.h" +#include + +namespace NLQT { + +CGlobalWindDialog::CGlobalWindDialog(QWidget *parent) + : QDockWidget(parent) +{ + _ui.setupUi(this); + + //_ui.directionWidget->setWrapper(&_DirectionWrapper); + + connect(_ui.horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setWndPower(int))); + connect(this, SIGNAL(visibilityChanged(bool)), this, SLOT(updateWnd(bool))); +} + +CGlobalWindDialog::~CGlobalWindDialog() +{ +} + +void CGlobalWindDialog::setWndPower(int value) +{ + float fValue = float(value) / _ui.horizontalSlider->maximum(); + _ui.doubleSpinBox->setValue(fValue); + Modules::objView().getScene()->setGlobalWindPower(fValue); +} + +void CGlobalWindDialog::updateWnd(bool visible) +{ + if (!visible) + return; + + _ui.horizontalSlider->setValue(int(Modules::objView().getScene()->getGlobalWindPower() * _ui.horizontalSlider->maximum())); +// _ui.directionWidget->updateUi(); +} +/* +NLMISC::CVector CGlobalWindDialog::CDirectionWrapper::get(void) const +{ + return Modules::objView().getScene()->getGlobalWindDirection(); +} + +void CGlobalWindDialog::CDirectionWrapper::set(const NLMISC::CVector &d) +{ + Modules::objView().getScene()->setGlobalWindDirection(d); +} +*/ +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.h new file mode 100644 index 000000000..717e1a22f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/global_wind_dialog.h @@ -0,0 +1,59 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef GLOBAL_WIND_DIALOG_H +#define GLOBAL_WIND_DIALOG_H + +#include "ui_global_wind_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NLQT { + +class CGlobalWindDialog: public QDockWidget +{ + Q_OBJECT + +public: + CGlobalWindDialog(QWidget *parent = 0); + ~CGlobalWindDialog(); + +private Q_SLOTS: + void setWndPower(int value); + void updateWnd(bool visible); + +private: + /// wrappers to tune the direction of wind +/* struct CDirectionWrapper : public IPSWrapper + { + NLMISC::CVector get(void) const; + void set(const NLMISC::CVector &d); + } _DirectionWrapper;*/ + + Ui::CGlobalWindDialog _ui; + +}; /* class CGlobalWindDialog */ + +} /* namespace NLQT */ + +#endif // GLOBAL_WIND_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/global_wind_form.ui b/code/nel/tools/3d/object_viewer_qt/src/global_wind_form.ui new file mode 100644 index 000000000..971d16c60 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/global_wind_form.ui @@ -0,0 +1,71 @@ + + + CGlobalWindDialog + + + + 0 + 0 + 195 + 64 + + + + + 524287 + 200 + + + + Weather + + + + + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + + 80 + 16777215 + + + + QAbstractSpinBox::NoButtons + + + 1.000000000000000 + + + + + + + + 0 + 0 + + + + Power: + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.cpp new file mode 100644 index 000000000..7ef711435 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.cpp @@ -0,0 +1,142 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "graphics_info_widget.h" + +// Qt includes +#include +#include +#include + +// STL includes + +namespace NLQT { + +const int directionSize = 35; + +CGraphicsInfoWidget::CGraphicsInfoWidget(QWidget *parent) + : QWidget(parent) +{ + _color = Qt::white; + _mode = Mode::Color; + _x = 0.0; + _y = 0.0; + _text = ""; + _braceMode = false; +} + +CGraphicsInfoWidget::~CGraphicsInfoWidget() +{ +} + +void CGraphicsInfoWidget::setMode(int mode) +{ + _mode = mode; +} + +void CGraphicsInfoWidget::setColor(const QColor &color) +{ + _color = color; + repaint(); +} + +void CGraphicsInfoWidget::setVector(float x, float y) +{ + _mode = Mode::Direction; + _x = x; + _y = y; + repaint(); +} + +void CGraphicsInfoWidget::setText(const QString &text) +{ + _text = text; +} + +void CGraphicsInfoWidget::setRibbonShape(const std::vector &verts, bool braceMode) +{ + _mode = Mode::RibbonShape; + _braceMode = braceMode; + _verts = verts; + repaint(); +} + +void CGraphicsInfoWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(QBrush(_color)); + painter.setPen(QPen(Qt::black, 2, Qt::SolidLine)); + painter.drawRoundedRect(QRect(3, 3, width() - 6, height() - 6), 3.0, 3.0); + if (_mode == Mode::Direction) + { + painter.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); + painter.drawLine(width() / 2, 4, width() / 2, height() - 4); + painter.drawLine(4, height() / 2, width() - 4, height() / 2); + painter.drawText( 10, 15, _text); + + painter.setPen(QPen(Qt::red, 2, Qt::SolidLine)); + painter.drawLine(width() / 2, height() / 2, + int((width() / 2) + _x * 0.9f * directionSize), int((height() / 2) - _y * 0.9f * directionSize)); + } + if (_mode == Mode::PlaneBasic) + { + } + if (_mode == Mode::RibbonShape) + { + painter.setPen(QPen(Qt::red, 2, Qt::SolidLine)); + painter.scale(0.86, 0.86); + painter.translate(6, 6); + if (_braceMode) + { + for(uint k = 0; k < _verts.size() / 2; ++k) + { + painter.drawLine(int((width() / 2.0) * (1 + _verts[2 * k].x)), + int((height() / 2.0) * (1 - _verts[2 * k].y)), + int((width() / 2.0) * (1 + _verts[2 * k + 1].x)), + int((height() / 2.0) * (1 - _verts[2 * k + 1].y))); + } + } + else + { + for(uint k = 1; k < _verts.size(); k++) + { + painter.drawLine(int((width() / 2.0) * (1 + _verts[k - 1].x)), + int((height() / 2.0) * (1 - _verts[k - 1].y)), + int((width() / 2.0) * (1 + _verts[ k].x)), + int((height() / 2.0) * (1 - _verts[k].y))); + } + painter.drawLine(int((width() / 2.0) * (1 + _verts[0].x)), + int((height() / 2.0) * (1 - _verts[0].y)), + int((width() / 2.0) * (1 + _verts[_verts.size() - 1].x)), + int((height() / 2.0) * (1 - _verts[_verts.size() - 1].y))); + } + } +} + +void CGraphicsInfoWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + float vx = (event->x() - (width() / 2)) / 0.9f; + float vy = ((height() / 2) - event->y()) / 0.9f; + + Q_EMIT applyNewVector(vx, vy); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.h b/code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.h new file mode 100644 index 000000000..39411590a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/graphics_info_widget.h @@ -0,0 +1,79 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef GRAPHICS_INFO_WIDGET_H +#define GRAPHICS_INFO_WIDGET_H + +// Qt includes +#include + +// STL includes + +// NeL includes +#include + +// Project includes + +namespace NLQT { + +struct Mode +{ + enum List + { + Color = 0, + Direction, + PlaneBasic, + RibbonShape + }; +}; + +class CGraphicsInfoWidget: public QWidget +{ + Q_OBJECT + +public: + CGraphicsInfoWidget(QWidget *parent = 0); + ~CGraphicsInfoWidget(); + + void setMode(int mode); + void setColor(const QColor &color); + void setVector(float x, float y); + void setText(const QString &text); + void setRibbonShape(const std::vector &verts, bool braceMode); + +Q_SIGNALS: + void applyNewVector(float x, float y); + +protected: + virtual void paintEvent(QPaintEvent *event); + virtual void mouseDoubleClickEvent(QMouseEvent *event); + + int _mode; + QColor _color; + float _x; + float _y; + QString _text; + std::vector _verts; + bool _braceMode; + +}; /* class CGraphicsInfoWidget */ + +} /* namespace NLQT */ + +#endif // GRAPHICS_INFO_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.cpp b/code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.cpp new file mode 100644 index 000000000..daccaa876 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.cpp @@ -0,0 +1,157 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "graphics_viewport.h" + +// STL includes + +// Qt includes +#include +#include +#include +#include + +// NeL includes +#include + +#include +#include + +#include +#include + +// Project includes +#include "modules.h" + +using namespace std; +using namespace NL3D; + +namespace NLQT { + +CGraphicsViewport::CGraphicsViewport(QWidget *parent) + : QNLWidget(parent) +{ +} + +CGraphicsViewport::~CGraphicsViewport() +{ + +} + +void CGraphicsViewport::init() +{ + //H_AUTO2 + nldebug("CGraphicsViewport::init"); + +#ifdef NL_OS_UNIX + makeCurrent(); +#endif // NL_OS_UNIX + + Modules::objView().init(winId(), width(), height()); + Modules::psEdit().init(); + + setMouseTracking(true); +} + +void CGraphicsViewport::release() +{ + //H_AUTO2 + nldebug("CGraphicsViewport::release"); + + Modules::psEdit().release(); + Modules::objView().release(); +} + + +QAction *CGraphicsViewport::createSaveScreenshotAction(QObject *parent) +{ + QAction *action = new QAction(parent); + connect(action, SIGNAL(triggered()), this, SLOT(saveScreenshot())); + return action; +} + +QAction *CGraphicsViewport::createSetBackgroundColor(QObject *parent) +{ + QAction *action = new QAction(parent); + connect(action, SIGNAL(triggered()), this, SLOT(setBackgroundColor())); + return action; +} + +void CGraphicsViewport::saveScreenshot() +{ + Modules::objView().saveScreenshot("screenshot", false, true, false); +} + +void CGraphicsViewport::setBackgroundColor() +{ + QColor color = QColorDialog::getColor(QColor(Modules::objView().getBackgroundColor().R, + Modules::objView().getBackgroundColor().G, + Modules::objView().getBackgroundColor().B)); + if (color.isValid()) + Modules::objView().setBackgroundColor(NLMISC::CRGBA(color.red(), color.green(), color.blue())); +} + +void CGraphicsViewport::resizeEvent(QResizeEvent *resizeEvent) +{ + QWidget::resizeEvent(resizeEvent); + if (Modules::objView().getDriver()) + Modules::objView().setSizeViewport(resizeEvent->size().width(), resizeEvent->size().height()); +} + +#ifdef NL_OS_WINDOWS + +typedef bool (*winProc)(NL3D::IDriver *driver, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +bool CGraphicsViewport::winEvent(MSG * message, long * result) +{ + if (Modules::objView().getDriver() && Modules::objView().getDriver()->isActive()) + { + NL3D::IDriver *driver = dynamic_cast(Modules::objView().getDriver())->getDriver(); + if (driver) + { + winProc proc = (winProc)driver->getWindowProc(); + return proc(driver, message->hwnd, message->message, message->wParam, message->lParam); + } + } + + return false; +} +#else // NL_OS_UNIX + +typedef bool (*x11Proc)(NL3D::IDriver *drv, XEvent *e); + +bool CGraphicsViewport::x11Event(XEvent *event) +{ + if (Modules::objView().getDriver() && Modules::objView().getDriver()->isActive()) + { + NL3D::IDriver *driver = dynamic_cast(Modules::objView().getDriver())->getDriver(); + if (driver) + { + x11Proc proc = (x11Proc)driver->getWindowProc(); + return proc(driver, event); + } + } + + return false; +} +#endif + +} /* namespace NLQT */ + diff --git a/code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.h b/code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.h new file mode 100644 index 000000000..1c30012ed --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/graphics_viewport.h @@ -0,0 +1,91 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef GRAPHICS_VIEWPORT_H +#define GRAPHICS_VIEWPORT_H + +#include +#include + +// STL includes + +// Qt includes +#include +#include + +// NeL includes + +// Project includes + +#ifdef NL_OS_WINDOWS +typedef QWidget QNLWidget; +#else // NL_OS_UNIX +typedef QGLWidget QNLWidget; +#endif // NL_OS_UNIX + +class QAction; + +namespace NLQT { + +/** +@class CGraphicsViewport +@brief Responsible for interaction between Qt and NeL. Initializes CObjectViewer, CParticleEditor and CVegetableEditor subsystem. +*/ +class CGraphicsViewport : public QNLWidget +{ + Q_OBJECT + +public: + CGraphicsViewport(QWidget *parent); + virtual ~CGraphicsViewport(); + + virtual QPaintEngine* paintEngine() const { return NULL; } + + void init(); + void release(); + + QAction *createSaveScreenshotAction(QObject *parent); + QAction *createSetBackgroundColor(QObject *parent); + +private Q_SLOTS: + void saveScreenshot(); + void setBackgroundColor(); + + void submitEvents(NLMISC::CEventServer &server, bool allWindows) { } + void emulateMouseRawMode(bool) { } + +protected: + virtual void resizeEvent(QResizeEvent *resizeEvent); + +#ifdef NL_OS_WINDOWS + virtual bool winEvent(MSG * message, long * result); +#else // NL_OS_UNIX + virtual bool x11Event(XEvent *event); +#endif + +private: + CGraphicsViewport(const CGraphicsViewport &); + CGraphicsViewport &operator=(const CGraphicsViewport &); + +}; /* class CGraphicsViewport */ + +} /* namespace NLQT */ + + +#endif // GRAPHICS_VIEWPORT_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/hoverpoints.cpp b/code/nel/tools/3d/object_viewer_qt/src/hoverpoints.cpp new file mode 100644 index 000000000..ac5de8f10 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/hoverpoints.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "stdpch.h" +#include "hoverpoints.h" + +#define printf + +HoverPoints::HoverPoints(QWidget *widget, PointShape shape) + : QObject(widget) +{ + m_widget = widget; + widget->installEventFilter(this); + widget->setAttribute(Qt::WA_AcceptTouchEvents); + + m_connectionType = CurveConnection; + m_sortType = NoSort; + m_shape = shape; + m_pointPen = QPen(QColor(255, 255, 255, 191), 1); + m_connectionPen = QPen(QColor(255, 255, 255, 127), 2); + m_pointBrush = QBrush(QColor(191, 191, 191, 127)); + m_pointSize = QSize(11, 11); + m_currentIndex = -1; + m_editable = true; + m_enabled = true; + m_minCountPoints = 2; + + createGradient(); + + connect(this, SIGNAL(pointsChanged(QPolygonF)), + m_widget, SLOT(update())); +} + + +void HoverPoints::setEnabled(bool enabled) +{ + if (m_enabled != enabled) { + m_enabled = enabled; + m_widget->update(); + } +} + + +bool HoverPoints::eventFilter(QObject *object, QEvent *event) +{ + if (object == m_widget && m_enabled) { + switch (event->type()) { + + case QEvent::MouseButtonPress: + { + if (!m_fingerPointMapping.isEmpty()) + return true; + QMouseEvent *me = (QMouseEvent *) event; + + QPointF clickPos = me->pos(); + int index = -1; + for (int i=0; ibutton() == Qt::LeftButton) { + if (index == -1) { + if (!m_editable) + return false; + int pos = 0; + // Insert sort for x or y + if (m_sortType == XSort) { + for (int i=0; i clickPos.x()) { + pos = i; + break; + } + } else if (m_sortType == YSort) { + for (int i=0; i clickPos.y()) { + pos = i; + break; + } + } + + m_points.insert(pos, clickPos); + m_locks.insert(pos, 0); + m_currentIndex = pos; + firePointChange(); + + } else { + m_currentIndex = index; + } + return true; + + } else if (me->button() == Qt::RightButton) { + if (index >= 0 && m_editable) { + if ((m_points.size() - 1) < m_minCountPoints) + return true; + if (m_locks[index] == 0) { + m_locks.remove(index); + m_points.remove(index); + } + firePointChange(); + return true; + } + } + + } + break; + + case QEvent::MouseButtonRelease: + if (!m_fingerPointMapping.isEmpty()) + return true; + m_currentIndex = -1; + break; + + case QEvent::MouseMove: + if (!m_fingerPointMapping.isEmpty()) + return true; + if (m_currentIndex >= 0) + movePoint(m_currentIndex, ((QMouseEvent *)event)->pos()); + break; + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + { + const QTouchEvent *const touchEvent = static_cast(event); + const QList points = touchEvent->touchPoints(); + const qreal pointSize = qMax(m_pointSize.width(), m_pointSize.height()); + Q_FOREACH (const QTouchEvent::TouchPoint &touchPoint, points) { + const int id = touchPoint.id(); + switch (touchPoint.state()) { + case Qt::TouchPointPressed: + { + // find the point, move it + QSet activePoints = QSet::fromList(m_fingerPointMapping.values()); + int activePoint = -1; + qreal distance = -1; + const int pointsCount = m_points.size(); + const int activePointCount = activePoints.size(); + if (pointsCount == 2 && activePointCount == 1) { // only two points + activePoint = activePoints.contains(0) ? 1 : 0; + } else { + for (int i=0; i::iterator it = m_fingerPointMapping.find(id); + movePoint(it.value(), touchPoint.pos()); + m_fingerPointMapping.erase(it); + } + break; + case Qt::TouchPointMoved: + { + // move the point + const int pointIdx = m_fingerPointMapping.value(id, -1); + if (pointIdx >= 0) // do we track this point? + movePoint(pointIdx, touchPoint.pos()); + } + break; + default: + break; + } + } + if (m_fingerPointMapping.isEmpty()) { + event->ignore(); + return false; + } else { + return true; + } + } + break; + case QEvent::TouchEnd: + if (m_fingerPointMapping.isEmpty()) { + event->ignore(); + return false; + } + return true; + break; + + case QEvent::Resize: + { + QResizeEvent *e = (QResizeEvent *) event; + if (e->oldSize().width() == 0 || e->oldSize().height() == 0) + break; + qreal stretch_x = e->size().width() / qreal(e->oldSize().width()); + qreal stretch_y = e->size().height() / qreal(e->oldSize().height()); + for (int i=0; iwidth() - 2, m_widget->height() - 2), 4.0, 4.0); + + p.setBrush(QBrush()); + + if (m_connectionPen.style() != Qt::NoPen && m_connectionType != NoConnection) { + p.setPen(m_connectionPen); + + if (m_connectionType == CurveConnection) { + QPainterPath path; + path.moveTo(m_points.at(0)); + for (int i=1; i right || (lock & HoverPoints::LockToRight)) p.setX(right); + +/* if (p.y() < top || (lock & HoverPoints::LockToTop)) p.setY(top); + else if (p.y() > bottom || (lock & HoverPoints::LockToBottom)) p.setY(bottom); +*/ + + return p; +} + +void HoverPoints::setPoints(const QPolygonF &points) +{ + if (points.size() != m_points.size()) + m_fingerPointMapping.clear(); + m_points.clear(); + for (int i=0; i 0) { + m_locks.resize(m_points.size()); + + m_locks.fill(0); + } +} + + +void HoverPoints::movePoint(int index, const QPointF &point, bool emitUpdate) +{ + m_points[index] = bound_point(point, boundingRect(), m_locks.at(index)); + if (emitUpdate) + firePointChange(); +} + +void HoverPoints::createGradient() +{ + m_gradient.setCoordinateMode(QGradient::ObjectBoundingMode); + m_gradient.setCenter(0.45, 0.50); + m_gradient.setFocalPoint(0.40, 0.45); + m_gradient.setColorAt(0.0, QColor(175, 216, 252)); + m_gradient.setColorAt(0.4, QColor(151, 183, 220)); + m_gradient.setColorAt(0.8, QColor(86, 126, 191)); +} + +inline static bool x_less_than(const QPointF &p1, const QPointF &p2) +{ + return p1.x() < p2.x(); +} + + +inline static bool y_less_than(const QPointF &p1, const QPointF &p2) +{ + return p1.y() < p2.y(); +} + +void HoverPoints::firePointChange() +{ + if (m_sortType != NoSort) { + + QPointF oldCurrent; + if (m_currentIndex != -1) { + oldCurrent = m_points[m_currentIndex]; + } + + if (m_sortType == XSort) + qSort(m_points.begin(), m_points.end(), x_less_than); + else if (m_sortType == YSort) + qSort(m_points.begin(), m_points.end(), y_less_than); + + // Compensate for changed order... + if (m_currentIndex != -1) { + for (int i=0; i + +QT_FORWARD_DECLARE_CLASS(QBypassWidget) + +class HoverPoints : public QObject +{ + Q_OBJECT +public: + enum PointShape { + CircleShape, + RectangleShape + }; + + enum LockType { + LockToLeft = 0x01, + LockToRight = 0x02, + LockToTop = 0x04, + LockToBottom = 0x08 + }; + + enum SortType { + NoSort, + XSort, + YSort + }; + + enum ConnectionType { + NoConnection, + LineConnection, + CurveConnection + }; + + HoverPoints(QWidget *widget, PointShape shape); + + bool eventFilter(QObject *object, QEvent *event); + + void paintPoints(); + + inline QRectF boundingRect() const; + void setBoundingRect(const QRectF &boundingRect) { m_bounds = boundingRect; } + + QPolygonF points() const { return m_points; } + void setPoints(const QPolygonF &points); + + QSizeF pointSize() const { return m_pointSize; } + void setPointSize(const QSizeF &size) { m_pointSize = size; } + + SortType sortType() const { return m_sortType; } + void setSortType(SortType sortType) { m_sortType = sortType; } + + ConnectionType connectionType() const { return m_connectionType; } + void setConnectionType(ConnectionType connectionType) { m_connectionType = connectionType; } + + void setConnectionPen(const QPen &pen) { m_connectionPen = pen; } + void setShapePen(const QPen &pen) { m_pointPen = pen; } + void setShapeBrush(const QBrush &brush) { m_pointBrush = brush; } + + void setPointLock(int pos, LockType lock) { m_locks[pos] = lock; } + + void setEditable(bool editable) { m_editable = editable; } + bool editable() const { return m_editable; } + +public Q_SLOTS: + void setEnabled(bool enabled); + void setDisabled(bool disabled) { setEnabled(!disabled); } + +Q_SIGNALS: + void pointsChanged(const QPolygonF &points); + +public: + void firePointChange(); + +private: + inline QRectF pointBoundingRect(int i) const; + void movePoint(int i, const QPointF &newPos, bool emitChange = true); + void createGradient(); + + QWidget *m_widget; + + QPolygonF m_points; + QRectF m_bounds; + PointShape m_shape; + SortType m_sortType; + ConnectionType m_connectionType; + + QVector m_locks; + + QSizeF m_pointSize; + int m_currentIndex; + int m_minCountPoints; + bool m_editable; + bool m_enabled; + + QHash m_fingerPointMapping; + + QPen m_pointPen; + QBrush m_pointBrush; + QPen m_connectionPen; + QRadialGradient m_gradient; +}; + + +inline QRectF HoverPoints::pointBoundingRect(int i) const +{ + QPointF p = m_points.at(i); + qreal w = m_pointSize.width(); + qreal h = m_pointSize.height(); + qreal x = p.x() - w / 2; + qreal y = p.y() - h / 2; + return QRectF(x, y, w, h); +} + +inline QRectF HoverPoints::boundingRect() const +{ + if (m_bounds.isEmpty()) + return m_widget->rect(); + else + return m_bounds; +} + +#endif // HOVERPOINTS_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/.directory b/code/nel/tools/3d/object_viewer_qt/src/images/.directory new file mode 100644 index 000000000..8bcdbc317 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/images/.directory @@ -0,0 +1,3 @@ +[Dolphin] +ShowPreview=true +Timestamp=2010,8,4,0,59,20 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/CollisionZone.bmp b/code/nel/tools/3d/object_viewer_qt/src/images/CollisionZone.bmp new file mode 100644 index 0000000000000000000000000000000000000000..570c14348c9f87fc2a63dd6ccd12d90b537354b1 GIT binary patch literal 246 zcmZvUu@S%^2t~Lg=ij`Ue4wWB$T6L40xFX|B`_y6>ETpkZF1Z-NaTymwfTgGTRr@De!Pb% Iw7LHC0Q(IwRO-n~apo7T?EIo0T5AnT@Ob8~ioi8e|2A~X3@dgH*6f`&K~MGUVz(SUD}RhVHiym$9oDH} G{NM&~_ir#-Zq~vp z{}CFN-kX52x)>vrh3I!8+%R~I=sSw7Ev2O*d!Q1a0!7er%EF*rB9#|FU|^^`B0^B< Kw)@drJNN~wT8)PQ literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/LocatedInstance.bmp b/code/nel/tools/3d/object_viewer_qt/src/images/LocatedInstance.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b44267a71201d6f826189180f8aee6a36ec3c46c GIT binary patch literal 246 zcmZvVu?>JQ3S*^PE}zPfCeRv#_(Z2 Z5F{7u`fx8zkLH-~b+0PF#0JRs-y0;7Xlwug literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/Particle.bmp b/code/nel/tools/3d/object_viewer_qt/src/images/Particle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..53326694bbbbc79797fc6a8e43a5ad0313ef1335 GIT binary patch literal 246 zcmZ{du?@f=3IVZ;a JeE&=T?*&_^h}8f9 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/ParticleSystem.bmp b/code/nel/tools/3d/object_viewer_qt/src/images/ParticleSystem.bmp new file mode 100644 index 0000000000000000000000000000000000000000..19345172e7bc4065025c6a3380b4019dcacf2109 GIT binary patch literal 246 zcmZvVF%p0<2n2D)K0d>D*!vypJS|VMvc)|x4I>F`;BLrqK9+HTo!q`$Yz?7D{VOOE wdrwe#+86^$@VMr#7s=5`eTf*DHSum~nv|btKssA6K=es1RiD1_mJdq%0wZm1{{R30 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/ParticleSystemNotLoaded.bmp b/code/nel/tools/3d/object_viewer_qt/src/images/ParticleSystemNotLoaded.bmp new file mode 100644 index 0000000000000000000000000000000000000000..23bc58d4200fc9910feeb2a31868f6a50fbeaaee GIT binary patch literal 246 zcmZvUu@M6?2n6wr6CzxO>u~<AVC!tE literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/Sound.bmp b/code/nel/tools/3d/object_viewer_qt/src/images/Sound.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7c379927a1f02326b2ffa55c4f925ca5cba19cc2 GIT binary patch literal 246 zcmZuq$qm3T2y>+l&@nnk|6M0AVfWF1u-_7Eics!?%K uB!QhHRF&w0#etbmET*rV|6PGkXD&ZjGb> literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/Workspace.bmp b/code/nel/tools/3d/object_viewer_qt/src/images/Workspace.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3215fe757c7e8ebaad8efdd6bd84d3a422c0972e GIT binary patch literal 1334 zcmZwGJ#HIU7=__uK!H+b2Wkzr2HX-zpVBouZ-d%KD!MRIe4C)_K&46p+=6=-$Il&7 zii2c0&&+*wH8Xeg^ZOs~f62DJzf0a4{dV&vzg{QaTu=W!J|^}&K3-3bhCJrW*Dv|@ z_g|U)E+vOuJ}{IVHABfsGnAY)L&-%mlvFd6Z jr1$sR>28~Qed2nuE7v5~x!dHLv9G1RZ2#A{Yp$msqI3X1 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/anim.png b/code/nel/tools/3d/object_viewer_qt/src/images/anim.png new file mode 100644 index 0000000000000000000000000000000000000000..a617d2397ea4058226287fa2bf5bb76f02596eb5 GIT binary patch literal 8182 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/animset.png b/code/nel/tools/3d/object_viewer_qt/src/images/animset.png new file mode 100644 index 0000000000000000000000000000000000000000..e16e70346648ea6314b3a4375792ee7f8edc427d GIT binary patch literal 9060 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 V;&nfp2<-p>002ovPDHLkV1jvnSoZ(` literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/dqynight.png b/code/nel/tools/3d/object_viewer_qt/src/images/dqynight.png new file mode 100644 index 0000000000000000000000000000000000000000..ef7c3868413ee70bd094940d3e3edb3276e4ebbd GIT binary patch literal 7208 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 Wx`D8V2rwZ40000d4o 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/ico_bgcolor.png b/code/nel/tools/3d/object_viewer_qt/src/images/ico_bgcolor.png new file mode 100644 index 0000000000000000000000000000000000000000..49eac810a28d7e4464d67b961f5036d3b4c23757 GIT binary patch literal 5327 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 hXWE%|ru`de{|5

UxftonZg~002ovPDHLkV1j9vdBXqz literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/ico_framedelay.png b/code/nel/tools/3d/object_viewer_qt/src/images/ico_framedelay.png new file mode 100644 index 0000000000000000000000000000000000000000..c72969a20c316d312180e7118e2cd8a51621c3ff GIT binary patch literal 6481 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� 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/ico_skelscale.png b/code/nel/tools/3d/object_viewer_qt/src/images/ico_skelscale.png new file mode 100644 index 0000000000000000000000000000000000000000..9d6455da243ab66633055f7133f53b9560946651 GIT binary patch literal 4321 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 P00000NkvXXu0mjf?2s1c literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/insert-horizontal.png b/code/nel/tools/3d/object_viewer_qt/src/images/insert-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..247889d4ab50c28371686397796ae27564445352 GIT binary patch literal 2108 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/list-add.png b/code/nel/tools/3d/object_viewer_qt/src/images/list-add.png new file mode 100644 index 0000000000000000000000000000000000000000..af5b56e3c4fbd6d91eb3fa6e337ddb89c5843854 GIT binary patch literal 2658 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/nel.png b/code/nel/tools/3d/object_viewer_qt/src/images/nel.png new file mode 100644 index 0000000000000000000000000000000000000000..d2139bb2557984f999a563059ea00f7e76436293 GIT binary patch literal 15587 zcmaKTc{G&oANDi5Suj~=>?OMxd-fT$$ui1b7)wmHC|eSY*{rU7BzZLus9*v^uC1-j9$Hv&?v*Q7=!J#&UiizwbDDV> z$cm1ut;CXK6gY{JnY0bRy+~XcBR&N|iIJE#@?tDU1`Gp0$r@!0l9SU8?#pW`I_-UZ zeRySuVu8RnLqh}^lc;pV`&n*h`CAqpv6tA5*a|W;F)o9~p#35Q)fvr!U{z)6n669}IlZ_2mp0?V;WI`${ z+c^r6xW%78+p1GXR)2pT+`hswli5;$9R@F{WcwP0zsF%M>4dIuEHDzdZEt4>@0Ar7 z7w?)phf6Q4#C8Vw(}40PZ-muhaBy#d4x~^T+Tx&6BDdw z(iB>5ZZv1U6VBhUQ&t9shJozbTHoyae8jJ;%uG%~_9~G%eAi9SEMXPf30ofegy0$9 zq;>Lbu&6xr{d;&S6Ctd-TSd(TU*E@M zRCOw`ne6VElad1K-GBcET!g0E|KhT|?{RW%sR^wg?@D3ctA+?Dl)tNFuXGa|3fUm? zuy8O~fgY7wrn6;=Itb+(#i&fqJU+*F;axpOQtLSIi@zHJpPB6lbb;|1Wp1hE;-upV z1tA&YmrSq}hz7KoUV6vlao9lva(m_V@1ww4T3VQAu|IC#>q& z@^vs1QRAvC3qU}D5$1fP9f7hdwU8|g=>Q71R2X`G3v7e|l~5=S&Yl ztIJUhGF$8mf*%V)@wdfw)5XOF6B!waJDR~#d)^8q;)5pJQZVM`=7gO-@Ge!3_4~&C zAPd5%h&mCNw6A0tM`I;KMQ`Sx>w+K7)AUtUQ}fNv&ITe$ch67Jo}8t48K5d*|Lij? zFDu(OF+bnCxc=u<{L{41#h){WQ`L^%=I=EDQ(Irbym!c;fbV6Kdz_LsR$CW0*JIeFgScRIKH@U2`GLW}oE0=3M)F#VwCsa~s_SH0zn&xJ6?%*9q zymwO^DddZAJb)#}iK?s8pC@h>l|6bS+C-Kb;kY_G2M8dMWfvibV2=X({0<&EL{}2x znYi%chnwrzhwbgNT0eY{eQ)-V&8A=_B~`{WdB62low$H%*~-ca!?ccN(E2Q9!EdxN zT7z3+8e2IT#h|dGZ~yPO!(bn;8=b#5G&Xo{f`Y{($*A(I0nl3GQ-xT zTFIHbyzuQ?4^~2=b7FQj{pu6Sgd{s)LqPL`4@h))Jxv&A0eb7>)8f0jwxq~CMTr)2 zfD-d{9zE1yqZjoekSLu9h-nSu^h1oFdNrmEg~Ak*&yf0X+S=NL@ZeyuBS-UC1E!ct zQd&F+`riSc0z+zDuZaPkcTx703KNiaDPjUsM6aTv0zdWXQ>(@}|AaYBCql-G1|E2y zcyeZyz`ZG|g7Ypm?OFW#@qB91!4fk0QwIJ532^qNzf)wr^eIq}_$KDQe8$#R_aF{O z=qf7IsT_T$+XcBUI?o{>>d!J649@ENr)MTIgvjNaD+|&}-qIp`H&3^%WL*`@sEV4umUPk)Lf_^5!i$?aOHQwnRh>>v?m0 z$0BT0XcN=!W0@4V8ozD9)l+zm)<0)Sm|rs(!Vfh!;W2ciGk#Vj+nm*~IREw$URO^K z-`LoQbG?83Pzgu+*8k#@Gx>p-{*#u3wbME6>I#G>`R5rT_)6y#WoK+Ud`d_m(K<7c z`~sfCI&$=AYnTm;;(~!#wj`d|+Y+;f`*rOX{)XIM4uBFzQbyg2igXV`#;W`{N62Ka z61ufbG99V9CFwrA{dbW*Daj5JuKv3XEN?^XSsUw!ydb!8urhU&F_Z{16#Y256oZH+Nj-@yYpQqgs@N`Fq;w}Y2P2WA&uHiGmVB9DTDW5|j$#!*h9b_MS09qD2 z@SjsqC3j0oN;2!~4VvFu8vvd&E^cm!@2enCrD`U0>g8%QiN<0nJQ19oo`y=UuPtbB ztCSAoFM&jRj#2!uunTmNJL-hit|w1*7ON_0@d_B9?;lUZ#V+mQK0{05KKiuuFD@_n zr^<6;wtn!Pev9G|kCjw2l;l|BD{cD5)NZ z;HvP-oSbX|+_j9AGRzVZ5Ti-BF#WJ^Mvkmv^LDcv;l8-N^-*fvt@{p?<-|e~-2Lxa zi0Ki}j6<062E&Q=HPEPc)5x0_i$8fgQ~z*mHa;zfuzlI>0;qr(RcR>h$|x&lK1pE< z67?gEA7?(6n4=|vV20wv3|GM2M8m{Xugl#-rwMMT?G0WEqEv0jjn;X{%JoNdpWDH7 zvxYFQW+0gYY%#>1b9Hrf0bK(Fh#w4`7XN4oIFvD}w^|!KFSrBms==N`0%6s&;93Os zRQpX>$4%|<_moj*@6L#z!N7}hf0 z1bio@T>Qupzc`3p%>#NxJ%OF^6XpJMz$8AWwV|oFwfRW_9p=uWy&F{Lh*&5q!_WG> z&4&Lkx|HuJl|BvBS<2UqN)H`NW7xpwk>WYGJ@MHwh~sY?vV5a$b-CMLv~iltzj3`G zA^2NXR=6$DSkdLry}nNn^I=R8)yh8PYfRBy*z&t4G~_k3yZ9OK*pqo;FDgJ!Pge&} zqx=m>p5F(rUPA;h8$*(4!|Og2#yUjPB?%;|L4^WC#8qSkY1N3(%CKE<G6?n<&V`k#Ny zeeS#7YAiAWNd@sT(W*cy7+B8q_xJZ*fBv5CE}i~hml~+z!5UFUXo@ECx_`tqb<@BA z!|EQt&!Um!N5A{S^MbYMJCYEi#c@J4QzRuhs?Z^9`Wn3|yRkHCs z|LBxS4Z`FS}@Fbb5E~Xpanzy{MM=dq*v3e70F*o;-bsipORw#g3NCjyPOr- zx6slPjiqq^jwJ8AUt@haGE@5KgTVMa$?+W3(4|Y|!UFgDhIh^?CNkeJ*W*kFF6xKv zEBLlvKt8Oxf5f=s<4n~4hhu&N0?*v-mS_f9tmCpx-aE1t z$vP=8B=+RDV^Djiape~J(Ze_8@37JXGjxwK>rC8Eo;=yV5bq_o@zFT#;*U+}^10FN zv)hiBZE&>~Jr8f?l!mCN4(YcXKcpGm5f`y&mKiGT|JiX^zxD)l^htPl{Z-Plx}_WU z%RegQ+>^?vSbA&^?lyUu4MX1lg1d9L>pgBN{+VK&*$#8`H;1z6a`oa1Yxz)TuzRi! zgIYIbw)(ob`n{dwF9k5WMo_z)yu;ZA!PUBtr23S_#t)RD&b?o# zz0pUQ@ibxj-tfr3#O&Cxbj)diQ9jQ*Vc`tb4Z9)Sm23(siHH^#rkwn?CscguIa`D#$nrk zlno+%UEW0dc$IsYZ@GxK?1v!Ez)wCKwhG#tTzzt}(9Hg6*BgQj{W`_k@n*nRa?kJP z&hzyipm{U4l7GLO0QKTByk|B&djqfZXfIw_OGd+CiL3lfc^YoB&+gLJDjlXHT9qt@ z>$-#bQYE)?@W-y{)qeYxF}wCq3x9RYki^Z`qset!(4s{sJChM}96Ds;Y#6VbRVG9{GsWsK;E6jd z7B}3i%9!jf@4(#Ve5?~CSQXL@x@HFH)6dI2fVr&oA@Iq{ydW1s(fe(#V#L)x6uFpSjy6R=G)$!-l7uS{YngerR>3OS zYngW0in64P1#QABdSF|k^|`GUdd9K}Hfen$W3po`7E^QTBViFTc1xESeVW+Z#yTQ_ zGh*4G<#Z^t%w4p;xa}MC!hA`pwkZ``Oai(8`d}7Q?29H0rq~ztDhOys``#JH)L#K; z{Bv%OY9nG(N}Y6)7#G~r8MSOIR2rsXzTaWOeD{Ha3xY@DK^htwF!U=cHbLRt3E1{W zC=|UAT}`~Ig!K8maNT1?qw-;m@Kpi@s=bX<;6!6yQ%)qn6qtPtT3I6KipI!+D>ey27kCx{;*`QIb zBrMO?@WctsuDm>q5x;`{&a6(P1YBg=9!Vtcg5_m*&S5aO<2jc^B)Kj2FYw_vntCIC z8$r0DJ6SS>D`!BW#R_cu9+X|wspm;SdqMi^LRd>cnf-=!n_rdflD$d?<46|=>BIV} zud2Ix^rHCSg#bi!L51?Q3wQ?tZjA$?vnmD#sRJDE^_07A_!}U-5)t0{4HhJq9>}JN zs1j~$f!0_$9%N(>)YB3Z@C%}MbvJuo0=Qtu3_ZhTxL2&HgW6wgj7bxgzkRgpsL+3{ zmCHH{iW<{{spk^In%6m<>l9-e!yWLX3%}byK#twLi%$d&s~tul;4YLqpOEb8mdDf` z5Vljl0=%YZ_^dGw8n6$KtINgVa>Q^rxn}>>s3=SBON2JQA2`hWF~yjO#wwiIhJ5XD zyg^gkG9l<(dFUXfUn)EYp)H=Id_~SR->tf<*93{V5D4aadhAba<$b&{sYsaj>3XTt z1T%Exb(+v>>B6#GVl^XlA>H+kINct4qrAA_^)hh{S}? zSvLD4=pDcmxIZxWi;G`T z=F+uW@Zm4JKx6a-%oGERNe~gie4!&LgAHMrSUNKI2nQ6ZwFq1X$*^t%?Bov^Of2s# z?D>rw7-LRvz!V=Om|Y1=l{|6;<4AW|ji$&?nX=C+2$?hqnaQII3qVR_G8}L`59!#u zrK@ugB>HXh-Z6amxpR6Ekg?kP;o%ckXk|yL!F=RK4B?p$F8YDEN3?P%whkM_S9-y_ zBNP^e-P`8FgQ0(J)H50X>N7FbD~U}}NV6pMw(ed9WdW&purhfk1u-YYmahg6Xy<}x2of`0n zpA_fPJt1~sIm|A?iW4|$PI+c>b$%eIii zGeMk&PdO)ga5sRlOZpJ1%TGSMgzKg^{|Vu|Zn;~LLOl?N2p2F4yw zdlsHi4?@JYjPhLN`L`%6S zsTUw2p_>lqWj=gv!L(rWCZePK;siqT69eH5j&!7`VF2vdAC&q{4_d{2KvWN+$6i3c z)BUxzEY0sr{Qj)0$YGPRQU41BDPbRAVN}STPS^(<#&aJk8^U{M7Zw&qSXX0bZ3b*7 z!ks9)yFp{;P7M_M#tHYPv((hdRx_Z6ykfk4lV1wi*~j}^sLg$B)cP5 zCrt@v%7CetJUp)CiwR}fYjCtn0U6rR6-*`IZ1xj2T%G;mK<`cqMwwC*CgnpVsxv!-KnUnMb{SS#_f7GBRK51$yE)y9&+7$ak&P!(h z9zbe6<=!20VUypIvnr94ABDp1CKex_mrTk>iHk{+k&^trK+jZ~TYAb8fB$*>K*Z(i z`E;k_3=-D;rvPwne9$l)D$FnLd6vFl?$k^V{?_m?J|`;+U$;vU?+reDlq~UAQ0g`~ zw~|#zn9bV7daK!xw2g<-4SMrUV{zVq>4_h-dPYhiKCX3{g7~i|Fi~K=7SQh#@H{*C z>J{i?TC%>laV3ZWJ_kR9-A|7TLcaV@XGTH)q?x?s)=A&O*GuPK#P_ zM#%^_De-bXlXdCx;bL=FYp&Gngc>#KEPS2z_CkCPJH5WLa1Gf zQg!#mXC--7p_FHcrUt;w4%S}7N$y75aY8v#lO6RT73lUj>S&Qf0-Z* z2dQ@=X`G@At58cVsif~@K*5M9lmXE1ktpmR-_k(jb+LxO-@cbwV=_6f;ZYQ5rReh3d*g#DOwf^cw7}C_1YMn` zt*wibO9SJW@qIS5KARLeo&I-pMEyvf$X`aQX|MXxf5(j4?jd zkrfTFl2O0cEoZ%r$Tp7{`Dn0dR<9jqywt*f6|kEfr&Zj|yRDr1l)0zuU+kS!<0Z!M zTK#A5^a@VPmfQk(s90QF!e9L-gY6nx{fmt7Imm)D*>#+1_PKdG?R)Hx1#X;V|Kwuw*GA=Lkp-!i}Z zR?QI8_YyGahsXRFqQF^FKwYCOG!dc*)SVMOz5v=>-0VQ7mi`#mY@i}Dfn9cDBz zl-RRDynBau;3cME@OC7KS99s=Rgr>CWRwV>+nE?%3*`uaYDqoch)D66I-$y|usu_& zkl%WhyZt~h3Er}ddaO!CzIUT!Tu89qL#1t~5Uwy>6?8x+L6qs|u)K0N zA^E^3wGlfZ8f4+P)HGVjsAqIe-S4fNwXfC`fH57wL^&NldK-4H>1WOTKpuD`LHp15 z6LDZMNbD_IhFD!5O|W2+XbKx89TuW59Ob>#X6<{wD_idbVh`V}#Z|)2%bd?I(slv4 zSoVtCY=Ajs0DHWe7SFMyDTO#81_BIbo=eh zgYmCKwKXTR!R*2wTKiVoentP$8>H;<>6&}^8Z;Uo(A13IvN+$pC_8&4lf`>A$3TNk5X9Z<-k|y6H$s{a@*r|JaoX<)PMuS?Fw+tUYBIK4&ulW*Hy} zmI>$4Rj-MKKjf)O8~NH1(d=YY@SW4NORsAm2a%mzPEaH!f>zhjx~GIpV$w!=9P#ES z?MsChiwoJgw<(dl1PgYb3A&s-Hg6`fkXcOLzFCXMlkc93`h~wT z)Y{)D0yT8l_ZEk5U-Q5foOnVLjCCMCxJX}?Wt;Z{Z)F628sUV0?mR#Pq^|P4s#{@J zAnG_${OMP4$SHc4ImSX~1}9Yjl`v-#b0&GNJv0{^ZsoGAOI$fmX<%B6_D-fL3`ao2dv*i9kV$_ zLi^tL+`&EAVEP4V;r$+wV&+h_iW`|DQgVaL~ zC}w49o5WTj&Wsv%t_k(JrZ!}mMJKwz2X7ziMRUsY#Irw2vtM6ccF=Y8=bh0a_x8ES z3M6Sp%M^|(3gik65_7W7^kg1yV1HHDCT^@NW8y12X{SzV66Va$e?BFD^bXKlq9fEs z!0am-Rmej{j=#^%5RLjuMsCa}OYE%8YyHWq8@xL+x-{TOe(D81Sm-|8hIGzaBA(W- ztc(3R%}b!(J8wSTlM>{gf0EPN4i*#?zJU?cL*3 z6%$o=tkm394d;3T_msU}p(-Huy_z_4mHq6OsB;^K&W_jn68ge<^ptq+0e$6tgNhgT z&B_hUzBv#(t#QNEt)MQ`f!xv9IB8Rf zJ!yVN>$>Z>Bl+0N#5-3S%Ze6O=A^58TEL+tIZI@Z*GF1s&JgY|+PXJV>}|iEpE9l3 zVMLL@gQj>r3kg>yEqWk7&$V6d&r<8Dd5~mPf8enT@VVa$LByt(?^C&XZF@VizjK(i zbBFOhxra}>!F2k8qLl{DLeLF0q1-+i(q8wJCCV%E=>F8Jc_#{|{R)y+2>oY%ua-q8 zXWbGRlDRlv-f3F;=D_>)yXen1Kid&lL};*2J(K)PwC(~n0B*@eS%FsPf#;%Vc9g~D%RR4zvQr5!pWNMv_@$~V}1B}wZs z-|tG)qJVd|;3i&DXX|%VN&xmCW)UKRHF%TscxH1I`P?F{@S3RSu3dc&AjLH!!qsum zaI=`G|0<|jq%5#Z*uG47%m12+1Xk~Q%ImXj)hm|8kRD;XY*EJK)HdmkN9`HCjZZY^ z<$2C$5cM=f-(UmoUX-}`Hy7$QDE}YlA53Q6cJS3RW6hlJjdCv_sT24TgNW#8SnO{K zQ0FRI_ftSWR~>3%AU(Tc$w@|q-?aL&;FQWBeyZdMB%T(4 zyo9D5Fp8(Y+9|F3;?eqhdLfM~BI()|XBn@)$%k@X0Fce1Rb9})4V~l7@#6ClJh=#R zt_dzkhd9{Wg%lqu1b;Y<;B%TTWV4656XHi%WjNjxm3&!J{|4&r+13R&ir5zL!3_vN zr>7GlKw}N_@83r?w*W(1X-cl-)xleZuo86%U_^h4W4AUtR7HBzbKw6L<*o{0@9Hvq zd-C>72*AU>0k6Wt!+)Qae+B3}!#q{bZ>&ZV5H_=Va?siUBNZwv0zAtM1G3PGJRUCdMQ|DQ&X?mR`RX`&u zy`Mh&3-|riEGBU=8$UZws}I57|HzuXzqgTJUhEp(pCTy0`Ie-(0S;Qzsa5(5E$s1M zr0DD?Bx}s1@fqGZD!MhS^J44|#;I9p7zV?yp<^{r_eGuH-wvsIn z04+xKJ>VMB$Puwcxi9~!s?te#8i6dvk^Y#{FRL*|Bqm&ru$ZSkJaF!`g~czD+Pc)y zQJScuHJ|1)B8YC>JYXvHuAQ~-C?u*!p4wwNs}M*&irf_05kMGySM$Fc5Ox#H>OBkaJo-ZA_2tIqVy3_VtyYkQLXo}pESQ~yUY z%Jm>j*5#&2qN1V^Ur~*L3E3d>t4BiVtw)ctxMD<8Q3?<>AXd2>DtPzg**M*e$;rS~ zcr~7QZk%KfEKKKJ`7^2lW!DCSzTGviyIK9-hq^HPa&j%Xf4tm0;7od z^5siadnEa#NzQk_{{St0L8N*aF~5y5lE_&3#T03kyp{5T3D895ND8Urde9=*lznYg zNAFC$IMEG0EaBuYni|V$c4F-t!es9o#PH4(aRyo23i+n#5YcbUENNUg-++H?d)iWAW!Y!=$0Zc zV;6&iIa_&?IjKWnByIl{yfFj*P$yWupTG1IyLsaKSX?WLlB(t-3sC1<5z1KeaQ6k@ z?SS^?4i+;jty}i-|Loo-bQb1*geUli;Rqn5C`<4bX;ZBH!9G zL|Ye3N5ASokjW`2tr}e`3u7QYrnA7w^ptwVt&yLrlKe+d;~2yckOTPC8y6v^PMupOXZfvWyU zoKRx>8%X0wtYIg{)vK7mB_zLJS9%%C^An`BLA7n^-f0kTCE>;zP`U^G3bN7u@AeuR z(^I|+-ycqtIROoRz`T@&xcB~uo7$MC6X zsAL5TVOIH9zI{xBPu89#zK2iI^0w>i>S!Tw-mcscJ{5g(@V1UX`UPG0JGTfm&*JEl zAFdl=$~8H2TYoH9ef?O5bMh4B8R*{<$mspetvT`4fF1p(#&P|IMtbkz%3xCp*Z_lp zj(>%|J18K92l?`<_we+!RF@cmoy1R1SdX3&GcJ+UV4e%GaYM*0QkVnpBY`X~Xaa@i zsrpVx6S(}lj2`9$c6m|M{(c!$fuCfZv$M9w*U`e9op|*{9K*loD5Br-Y(;^_>$Wll zbW70k2lvJtlE~=OX;@&x5tfhZz2nBCCNEY0=djLXHB^!I0l7SopqpT71741eO((r6 z6`Gu;3Nl8mSYy+;9is;9=Lc$=5F>$P_P8&_G!}5GL{{< zN0TM)#d;;iE62N+Lk%7fGh~N8CV%(@lX8G~d8Z0lfUoPsUY0xYr2dQw*Nc)0^4f)& zGp63H)};mn>8Ylq@ey#*2_Kf1BRyjY&$ixJn?3}O;mNuXqo)*FYHM}pO%u{5E!ekU zCp-ly_mHSN{6P5HB)scCc?F}|k;gHnY6{_LS}a`q1Rkr6m&p$zNAk8H?h~l-Bk}Udl(8F?64w;0M$cbn%=SxNYybxZj|Fm)80>xsW z#1zBeKvzM|Fv!wm=YBYza(zeC`$k5JG4n|y9HLV932Evn5YvQJ1BO0AWp99_T;oxp z7cD?}Lj!bNl#_Plv)wAnoh;0}rW#l;WiKyWneuBwnoW5M36(P&Z_Jt$CCm4`(Vl<| z2DtO(0dGB8Lu7Kcw*2E(ziM!sUGCzi>4jO2WULt)DWyX*_{PrJg1n=`{wL{qQ=8T< zlg3v|={x6w+vizOu=)oHt>XfDKvtDReh$r4p`3GsCpGH-ab*L>9lWN3o2CsbQ`?s; zqpD3!dePLCaL*S@Pl{0m?h36@sPjm3a;I+Sgwkne5HbN8aLf$=F2q zA7)~t_6tv!shTaG{=R+V0Ym+r^za$i{LYz;wIhG+I;swTP<2+SQ#~^#RZ-=B_s2T! zXodEOM(88G7_q5Cch}|xnk7;+Ixq>57lUubIe_ZTgwH2h-rS=Kwb-}gDnb8Q!HSmt z4qb25J?SY#)1R6AzLY=Zd!L%1iLV@Y-nLE)cgPJNq(`5u#v~`|TL9ltNw)Ds8)fa>>@?y6S z-6{iBOK-Tk50|X{OqB-{V)qzBp-$`#tBt8^?v#Y@lQNfd-F&|gVl-iBjrq_G|KHeZ zll?aBN1FP2{a))8I=j6&clN`wc;)M;2U%$Kq=S#W<+rYH*Tv3RM=b)?S9g3pGK^JeoDCYM1#XGZ zZ|%`~o?!QqBUkupG1ySLfGd}OEl9YHz3uR+@>QCN{w}7EK~N9crSDz9r>N#sj2mP3 zTlXK?53030X9C<;;@ySQMzi)Wb~PC(Q`FyvoE`Y7h_1!{tWp(O+dDB$Q}#VpC9>8u zE;lhCSx_^AE6~8a&^DAB392XljNA)S%8i0<9fOW6wP5<{G>L+V|0T#uf!O#F91)oBZw7yKZ09#C<>!gF7@ko@`g)14=K z^Tgkpao%&$p-Dea>q0i$Iyop?))Cg-cQdpMXW6LJIuLH!4@}bc@e^_J%W!Uo$^%^Z zMv(t%O(i(A{2=4buk?8s7%~aS*l;#CX^$t4(>iCYavh{S0m>U0g|8aH1>=c5>_nplvKD z5O$N1Z_lvx;A!FswKRveE%dtpgp_5<3WRd3jtStW|c3Xe}!=VZ&c9%5nZ6Kl0K40xjw>=q~#*&)aUY1xQxg?3EShNqm~z;Vnb~&G&^{+YF8@ov>&hi%CyLausyQ zW7kCMmaof>eupaS##m^<_k$>}#evQgY_C9OxZGCe)(?_frXu|Z=c=Z$63lG42xQmG zqJc|K0P|Jjrg7MW>kR?~xfD7w@1HmilHDFn+g_(u1vvbURvHUU5C_KwsaDn{b5Y5ja|q5@lK+|EzTgg zA%+gA^5s`CnHyk?45uwca7XXLR-Bon7V7N`Q>i&$RL;3_I43N#8`E6keIcmQxMJx)ff z7hHrKVVnjZzMIcc?zr?Q2!HAL)3(L4*nX?YZ^aNOw$=JI5pdqk9Pzzjd?u9y8ucJM zJKwsGK8Yr(RMLw8q=FE2OJ3hBB}6lB?SeSw4t*<>ua|I>mR_7`V3l3Z@9xG7Jn-?4 zi~OX#x+-0^orJUbM-Nv{!@TB!cQOLDHQO|M+IF@L+F}b~GMMtmnK-*-ku6Fq;2c;X zKu*R`OmnU=v*;PunUj`;$gRM?!djc8qpTl^8-8)io08l|#T?*B~tby(tjO^fJUWHn!B_1hM5_eC5BHmg{8S13UiBH)wwV*AEjRGuGH* zs2^`sY?TP+L4oJVC`ic2J^#_CLBiJh>@ex?UtX|6f_fHLu9$H}DWX&NE}-H*%IaG$ zLq~VV3C$BCfx{YdJUEHv9ZqS3G$^+k<RtbS>Jt40+&G6zalUiu|<{TBa zUnDKgj9|u-wu4qtb~8TsZJiAuFVm7PTRHyT43kOhh0c@d4rf4n)*yStz)B9}&<{Z4 zR}4G8CE|}3H=mSfC#?2gv%q_(OCAu`&xCk(!ojSk-=1-Zfc;ZJ9Y87sq~gq*OX8h4 z$=3L|MCZ$swln!c|2$yx2W&`k_UlWQZ*aGQuvpUa><+$x+Ey_&o-K?^yAd(3KLTIh za8~3+*`Iwu%Ew(PFX8GQB;3fwG)Fmtf&+L1V!l3SB(1^MR@<*2qFg0r1rAaC0-&;F zgb5Fmqe|DW^oR0!ID2RU#L$SCA*yeXfW&D(fUi|0O#hQ%Tql!;&8Z+1HF6L2n~p#+ zU&n2>{N(Jfz{b7V7ydQd!BOA1Bh?ILP&$nD`lL8J+3S%ngjvSdQb@CnDWF@!GK zN-iBzKqUozdjt{Hkkqebh%8I9-saPl)|S(R@%Nb{P|VLq_EjJNoIbI)x~0W34ke;* z18a;Gg~FHOofCk%3aL7GSW}*$q4P2{-WOL3MBQ)opd7-qu_nc2?d{>B=H`ojJI*TP z0fN&I(OI9_P~p9=z;9VjFkW(-jGzEDnap6k@A04$BqFGXRpk_Cmcv~y{*&tGq%}zNsIQ=foT`CHO zr9M`t-p)D>6x}mD|6K;hv^@YyOoZ>FAnyu*XH9^n(ZUN_U!!RlC4$8S-=-D9&v$TDgP<}(c z+kS+Z*F#^Kn+L@Zh+oKpKc6Os0z)*&p8NNpkwiV0Ys&WJ{oOH?a!5)1dAVe5pHwu{ z)|SkN1uM0v#n>pU)M`!;FE4C1Y&R=KUBPhn{;*eTE5?K)@>hl3o`BT6Vky|R)K3Tg zp)wWpPWvlAUO*B01RlwJduPExUy2s?3s^^Zwv)=s`QSGj6YL2r>g3zxrOxxQZ%;)H zK=TS|VQi_2L2+!HTYC9n}vd_2J3K#G?XRRdG#xFHJ8hWR&O%dTI!aDH*qe zDxZLJs~h;aKtQN25v2di5CvEHhZ(w9QY*yx#f$!^@ViDni3zy@#U-m$WJ3&rr7$c+ z-%?238`UzqGs13X2A|^)H)|8}jMKw(W~9mfhpxter2dt8!@Fm9oABB$ z#vZ(Q2I2PYDzEr;!Wvz$Y+B3s_oFJw^>oPfq^02eF7i@C9 zdgH}9Z3XUhw#SfKE?@Vc8`YkXo=$OZjyKqA{d&-c6{bW;qR+$n1h#12Q92?(WkYAQ znm!TGnNlqzx=k)JM~?X&TZ8Cg8Xh8?=Y-lBLgYtKHSi;x!myDN5QRm~I@pS1({fxK zhoD_sV0$;=2F31^g4vfuz$HmY`BAul!f_>a_u_&Rfy{utX%pu?Y=BWHu$oLD2g=N9 zjHh*HDd~e%k!b#~ZpFjL7}l}YG$Gc=X)RuPFd5=fVEZ`>{1eo@ZwLQ8+?|t$-K3ED qiLHEpt}$vqsE==;|M$c&4Fo(6DE0di@OlB@zYgtlu(@wVVf-IpnGuly literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/nel_ide_load.png b/code/nel/tools/3d/object_viewer_qt/src/images/nel_ide_load.png new file mode 100644 index 0000000000000000000000000000000000000000..217259d0467a6d95f04b921e9157a3cc2e3a990b GIT binary patch literal 45864 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�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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/new.png b/code/nel/tools/3d/object_viewer_qt/src/images/new.png new file mode 100644 index 0000000000000000000000000000000000000000..61db97ae49ca9bd542c685c973df94e0e0daa460 GIT binary patch literal 2461 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 b|7ZIz1mu#5-Pb$aXy!D< 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/pqrticles.png b/code/nel/tools/3d/object_viewer_qt/src/images/pqrticles.png new file mode 100644 index 0000000000000000000000000000000000000000..ed1d41f5b2c2e3d6508c3f94e0f06796c7fa7cf4 GIT binary patch literal 8318 zcmV-^Ac5bBP)ht(u001AS zNkl zsRA1q?QS|1`V zW6$SlNKq!~n@*6QmBm0~8PHO0MN=7s{=TW)ih>L)1FF|SA&~El`o=Z5tJeVzO{99a zE3|eRUqxq>JENq`32zlSVzAhn8d|9VC?S%?r>|v#m?WkEF$QSciDSfZK)H^eU1bH1 zeVz`erD5xd8r|4R8c+$*S;I(FLFuVsG&j;2bV6eZ&_D+z`T(74pmlfv8$Ge7V-;kF zD|U5}{*4Y$rMwO8?#g_lGZr>FL#}tk^aeU$gCh*(WJNI@P^ewr+J=^yKnSu!>E~-5 zn?5h&BSJnxGmY%1qH)#C>ndoq1*BpjjP=FP2-KH+bX?C0DBa9y!mG~>@AkMtcDjm& z(LlSHAs6vjmx1qB2veI2WQ!x>C+M7f8yLcQY{*5{GE6Y)VwnLRe1CGQB1VsC>evy1 z9~=Q5CjA``^Y4UcR6Yj>w3d?2jC>`HuPB8_eI@p^S7TjUC7n@?=(Z~SXGUO9)iPV0 zYIOm6S3u;df4PqF<*3qlb=d($)2ye=b@P>g<+!5A;}DjkK-8W5J3mtvxsPAkdJCA8Bhv4xW8{0}mqr5Jy%$Ibb8e4@=LANg_O!jGO# zTfF_N!KR8V$ogvKba^3M%8FPhORe88X9UN%h)H{BQ>^|I_SijP3(2Cjz=CD@)^wzK zSB#5n6u8i{TB+_PV;Y>;1$bOObN7=B*i5kEE8DEC?_R!^dzn5rINHnCX4>S-eYKWr zHKV1~29~_lkdf=q7w!!ttc4ol@pl?uLk9FzK|1}M>~z$lg8gbh>z<2ERtq622_l~<*#>LHwDUaq2y*!#%960p$PAHmp~5JsFukFP2O{7s5~&Y(p+C8 zWJL}Zi^;XUHvp7DYWUY`bK*ATVacYBJ!hNgK?LItbSZ2l4uV6Vu5V+x5cN z;S6nXEz`~6v)6SsQ(=d-Fsdhn`oi@>lh-Kfn_CR3-+om^r?XI_c&<>de~|vgIo7={ zUF!-k+f{_Gcjw{8&Mcg3O@?fb$A``FI9Zc{L&aIBmM#1)8IYc7JuXPE+BCkGXfrPD zod#`TJ49xT*de!<4rq18;db2tF3_4BFTR#7AA7x4nFTUTOD`a1m z*xsKd(4Q^Tp&Sj_Jn_e&Z?iOhj+h}6uCRre=ANmc)T~*O#1YL-SV0zmdvURAhtiV) z><*wvaAJ!cFfKVk5ZDEHvO8o)K*2$NR%H*7vl=3RD%6*bILr!s4SVfzinim`(2la3 zU8y^w;xddbAp??Fb`w*m;7IGi3aAA|R9tCzu|EaR*F`~X%!5*!0YgzP)Oe;#JnKd# zk6Yz!Jx4!{-$6|<%SDgS_z^uFNb?aoVU$HOf#11;g6XK26`$Pvm{Jk>Ko~nqP!SZS zmX;U@rIeA~%X1yq8H&|7386@Y^7Y}RIbe60HPSR(kGi1oI&gwGxs1NUZ4#WgZ4VP<#mXV3h?)t>tl}R+@h5)@`@6VwAB*cRE2~^aaxZ zI!Zn!f4v!{Kt?Hmd;fAqv=VCI28h7*REkyB!ABo}^r+Kdby|0%wLoGY9d?w*9j#b$ zf_ioJk|R=y?em#X4is7ce%YI@x3}^_TdlbmO3HPfOoG_&0PR+Oemg(EmiWeHoU1yR zLdk83&DaX*+%y5FSFuM8I)_8`$x*LlE%MsH8soea%2-Y&U0S211790Oq;hlVyE7N zw=1H4hazx&u&;X3#c3s@Uq>C@q|47NYi@q#7B%h;yrCoi|kOfh;~&A zYd31)9rnuPqt%$cw^9UFivGY-ycv=M84-g`5oBU`GPJPeRGa5T574mvtWmr2*)BPs zT(eOKDN}G&HBQq3^6H8uWD>agZ*f4sRf=lctPbdT--ronG% z_{!Z@dVXqAt7bt>17zdZ8@5xzPiQgfU2$f5ModkPimUHbCtWi%X77fGq^k^aD+V?*YLXs zE^@y0ArN&;f%R^1sEfvHRF5*ffx@NBa5}O>JIv@0MRyxI!aI!f0gdXDYZmbwbD^ z`FQod?TXEbTGj>DhLbE<*VSIa_y*!;;yP=AG3rHTo}=dsjs1_V@&)tJD;DKVdrfEh z8W#0I3#Vp)T4*DJfGMKSunzLNU; z!vj+mdEIU)uh}j?m>)CWXk#PU4W6Ek2vsGsOD~SupK`uNc9y1C7O-X;RMo5$>+Adw z)#o%7-RHaz(P_8PcW>_bgD-WO_P^MrxbL?gi*R}NYo(XdO1clG>#rlD|A-nLts4$n#RRr6Bbe8}>2Ud96Jf^4$U zhC6x60Rc7F@G0{J+g~Yju#^R>A{|OrgN+u`?}F$-x1z{i=ijGIxXYVA3eIp;3VpLLbD4hngo1myf!iXE`fD_g^{GMIb8xW>r z_O|lT{8TAVY?r?tYO{PRD8TZS6>gRR{ykg8yDZ4QDb{ZAsrO_!D>4h3665?Bb+mB+m^k?2#T zkO@KrhEgLitvtwv2YaXiSO@N|6XLG*0w>DE@vAZ+U-_D?`JLyTEXNxl50>IR>Vwt*y|85l)~3Ms@cNad43A0v|9no-7ymX`aA2wzkNO zOE}>FZDBo*sMzd-Z*kbj>b5tQ(IF*Ww)-<1ggpLClj;5sH_Q9Z^~t+m@0F*1(Ip2C z(g{hBen~L-CE|2I3`#;GU<_vs2#sQaPU4uzi0;pa;W|3`pBhxJ1KIfFzaF=fPkhJu z!X~!Ry)|NV7a6#-9NLk>&&dBaVr2PVoSp_#nkvQ(8m5c}k7-4`t#<>&U%~8WY~?8s@+V$iNUhMU}0rWV-qJvjwsajIltD(gCxj!grz&f85ip z%EN=|;w)R~OfkMPNm;$S9EOKz;FVRESX}wCwA2L!QvW5Mp>x zQ7|EdLF$7GV@?)`ToNmHnOyD5@>myC&H8P2Sm%Ac%Xo4D2|cS(*kPv?wcDbk$Ei2F z(IBsUw88Y9Kh2W?!}8u=4$D)objq84SSl+Toh_l^OocG7$cqdVqF|Ipj~2r+T!3GXm11$C7z+~xh$TShT>fp-1}h_U!_i9HxR*5MXFP@C ztM_*kO1w%$fDc9*E`aE;qFYqsO zIG>B@DTGF#J0HV+)DDBCxM`==F`LN@IhKRFCh365La4ndm+62iYgf=3t<_}<^)p|X&)c2HbgV$!y;f47z}l- z2jJytWk6B{14T7RpfwlX9r@_!$$_Dj(YTpW8_L4Ik!*C1vYir2PwL+=6jdxcg7by3 z+B&d->b0a;K$7m?Dzkn^Ng=WPS(!7~t2fc`Z646JFxtdAM!j#=6%KP`#wIc$cGL@r z-K(@XIwGle&A#{+7kSl>+86JCZ{GY*Pqi-Y`&y1^cu%mLyDHMW^Tyc4_!!8T2r(AJ zX(~1nnis3TCyn>y{J(D9M{6DBLi!nv0j9!GDHQIN(GnQb;f%+(p&f!q@t%BIO zR$!O68c!CyynPMiwpGZ`(}GarJKdGIPIn~rukng&b$dH!%-b^k)nZfAtfwqoFJyWi zWJ)^BNr|xJ7it%hW0r1s9*Cylu>sJ0>_0JJG50$2t!O6`x|t5WG_aRz>S0?N$$`cc zP$&6bN4IXY9ywiArt3n5dCmxHl-r`l`X0F~vyCk2Eu`0D{9?5aw0s+ZQcv7a=+5Y` zgv@eBc{Za=sC{(K{s1A41PaV~z-Qi59NFTIKko2=-06w;=T{?$j=(lgD9N4fDuKi{ zx9bR*(CWGvwcaRGD)8ThW%z1Qh1OYF?+}oet!~bUhD`8*4D-ZY(W~eH52$`?|C*^Q zScKdN2M4AFndZcGb*3%R9|Mf+03$oNjKA9INuydGT+0LNe4%w^LK!62>(Z?E2AeX4 zp*~qbU6N?2iV(739izVnzs*>QRq4D?i4SLVwwd@s*!PCuYg;^VVKW_cBH3=?P>?EV z{L`B`?Ctjx$`zgv32iQFatm8wlhb5Ev-5A_njG&)YI7;in(%hW8(D+2DhEMBl|(Wo z%@0?{`EfW3#`j{W{t`o=1T7CkK4f2%mo*&%xR3CH!n&Z@k%3M;)`bp-%|&v%)Ox!O z9o8J)PkXl%2sjj5d$|~0579^A&{-l3ZG{Tj3)R%xBz&+jMLS;=p~zfM{Gf=1u8Nf3 z5DK-7xnCZOtbqV(axa*^KNGm{r~AvyH>Nwu&93-hkFQd9fD7O24Kd^=5LKn=5Ro;S zO>C7B-l?{@=Q*~^^-%Lmpk-!pUk0V`ER769d1N@!^&%J@$$^OH$qcbhg>gh$5Lp$( zNs%*0JdH{Uxt!bIG&0WmQn1MVJ0U9!_(hc`c8!$V1ynI-^?jZow#}w!4WFi%%e-)kTeKbWjsFXrhVwK0Mz6={#RLjmf9MDKscMl+FpU zGIHq&76+n=L?{(WoDIAn!pP!?by(-Q8e47M5#>N;I<8{Fw!PG2Fl!&zo#9fg;cT@l zWJ544of){$k&9Jp%}bEsl!2;K1+L0Fp@fFseq z_>I38q?-qZSF%p5UJLD_DNw%L;_6mGtz*8_x}tug7c9Lg%7v4g0?b=FT;=e3RgTnY za_^+4`RRw-%^$vVNZx)h(L&10&UD+f2J4us)-qB$8yY#J%tQ)xkhNrL9UeZOYbU?i zziwg1jfK!(Y2Hn`?(~H+dznvfeG(~p2;W2)0u$ht!4Rx>Ue>=KyLFDrB|>}ZCM9R?*{X$FKv7zYf45O)KsQKava7BVsO4H4F6IU zC8jqdD1+;ha9>pd&Tq(oEJ?;t0-eCf zw6YNNCE?+bB2~Wn)TrsbU+mcta_O1+0PbM8HJ~6 zqaZgXz+6pL$h4E=`rV&tcX@2a{ZB2qkOi^gu2#y_45nlh2P8kP$7kJT(2TT4J@aKK z4caJ0!FZKyHh*$FnJz)-~$iDDfY zrgSX~`-}@EFdF^5$Y?6GVKQMXS)Cltf_&y%jf)?g+a=$4X_I{M$zl1!2U?b#aQ5f3 z^6NJhnkz`TVn6ZIY)@rfCZn0oio2PqFvJw-_k+WTA5s~8!&ZJa;)@3+eDP&E2IGO0 zr|T}S25gM74n2}%X?p^GFpviM_^D9S>p#wrXCBR#&we|_JX@~FkuvjrgvW6Bwv4aOuqii zko?KR9rDX3^G(<8j5D9u5@YGga#)}iklCK%Y`iPt6I>~+S3-&Aeu$ACqa&u+9w+_T z%l%;JCp!oX1ycv;-6GVA#EW$(My{L_)^HjnEIYtdzM9)l>fbWyYB}?RMESyZQyP+ab6o93S2`946(t z1C|e8nX|m}!i0R|iEg>CceN~I?w5xNS;(SR#&%Q8u8~EZALk5{zlwMQ0R|`|%)wEA zsCs=F41ypV#3r`0@kpq12@tcS=pp9zwj`#@CGOAZH&f?wtj{QJ*tAL;NyQ5XHX7xt z55<~aeK1+R^l$0%g~u!8J$EEo=GyHot=Vkj=~!gs-=uD6D`saZjiPk+xyT)`9z9Z| z$w)jjMjj(^Vm}=rpEy~)@YGEerrS3MT7LA^Li3NlQef8WLVvM($HrLO)TS_FMzxQ~ zFIkJm%vIPG>xAq56s#o>Y^0&2osLl&I;+=}XsBacNCzb=TfL~YW7}QE#kNjTLy!DK z1{uTGyVe_>#!~V6;bt3o=D`^AtKUtL&;OuEKL1#yeC&ZNd34s*%pu6qngvTuA|yrB zPywA=%xn7uU*z|919U)t1Vk69*%vI_h9YrpGFg>73mi<-q}wRl%T%GT>~FVSoG#+~ zRFTVCf$@ybe#F$^Wkr(@)NZE2K$yT}tSBQ3UM70<$P+4GV+m2#8!#VtfR6DjXmcFK z<_eUdkz~lP?Ql1p{kItT{6jhNg~zJovkzs+t#nFT3S?gao+S%z*^rFv+7zCTVyQae z5{vBBb1WR%5G(m$7^a3JAxC1C@Yc30ZQy8?i_fNf<$F_E*3bKb&@#;2Y72z1${T74 zdwSMtXtB=F0#pbBA)F$IqZu_CG#H6BV=?$oVs<17dxt2Xra0YA$Kr+=GHxyr#!;r< zZ5x1ln#m6TPy9?S1IidvWja>`ji=+cJ1SNA>^%XN=T1k;|M*V2eDRSC`OWKmOaols zM5%IYnCUW>3#B_th>Q>+nrT-n;Tv+1cnvZ)`&hwySHoo_1h0y1`Te1mn`WKmARRDQrAChxFs*Z8tW9REq0F7FTAC#;zIFRT?eykE=!L>T z_U(~yd|i)jAr0GERkVi6lp6mG0D z&rKykzH~>h>EAv{nDKGPj-wiY$Xfw)@Y@9tZWM?~02N0sKkbAO$3h~lgBXe{EdvdiW zY`gtXJQfJYA^n#ek?hr6d?zb-J>?{4e7l}7ywV*COIr{E^yqg4U;OSW833jX7fG7%5c9P@TG9w+Ci@pq&{t@v09=*G{{km-D&D+vjs znbm^nah`j*rCeBUov~lsTCK?OJlsE;&F`181JUq^h`;f-KqC=I*HTJAu|v%rtNcEDOg}6+fo})MA1zaz5sNH>o$B3U!*rXeLd}jwL|eIkV#YGk1h7 z-qi0X*=-g#WhzCRSyVTrQY*xNfFrY z3UJjFokeIvxqpM`?o?AxOu?BBuG2anBuOSz5;e%3^p+vNlIyhuEr7>Cv* zE;f}@V8lgaW{Y0clc5k8OG5NSD&Cu*ievOFz45S&GV=WFeLBczmeHRe{VS*vD?i47 zHZmZSLsRk!I+Hm~W3^5BoHk2@*jA=szCyK`EWk62z8uX(9|1y}DgGRPGT%gJ6jIFO z(NTS25Z~v?PvuzK)}^+AM60ycTjod#4skEJKQow&Yx`5h(VON|GpG>aG%!|54+h=tGMCdwRa=C!@8C5 z3gFO^bYlbqcHKCbBxFu=n?yhTl8cM2@p)K`&qAuZcc0dBDnN05{ld8##RsxZz zKkJXayyItR@HYKN46M*h0d0VFA%%+Y$D0_{jKAYwa`fBW*;eQeI;xniGevB9#a8@} z*xFeHN%JKz`eG;r>HwqG&-d-IOX^OcLqs75qjJ`f;*iTfxBlD7p7dp;QN^-*wD7h5 z6sSG@fABw3b;Vj+TL&rH$@D2^`p8?Ce;Db3FSakXFSakXFSh@m+y4dLaEWe*8WjEj z001R)MObuXVRU6WV{&C-bY%cCFflMKFg7hQGgL7*Ix#dlG&CzPGdeIZ17bG*0000b zbVXQnWMOn=I&E)cX=Zrht(u001XPNkls{;HGWI__JUl#DxHgJ~hNxIja4^1(7DXDy3l>kE%``Fb?*~O_KZ=e|{5U>s z;+KZFq%PX%II&tE;}mI#8KjAh`9T*G|6TO>39fO8DPLzyo$=Ay;w`N2U%zLek$M)P zHwskRC{a*^n(5RU7J^IQz}C&Q|XN?OluH^>tjU0k!ltc8To^ky9-`Jg3YcvcUW8oS--jJSNi`2$Afj)i$!)~w$Q>o#tI)$2CF$~7Bc>B=>bnO6vtrp*FG1O(!> z7%($_?|xm(_}==sMEs2yQMe{rsL~jjN)ydgh>(BT5f+K7poyZhAY5k@siR_8xWV`t zuGC^&L9>6T8fMI%Z!Rl8Z;nYwHpM2Ua^)3OT-C+PTt($2?&77(TutpY?&hsK@ZiB? z?&-4zuA!lkt4F(i=briKFDFd%7G-j{+7PIUtA!b6ddH=z~mN##jfQ~imH(A{Mf^g;6!ubmq;nbNjICs7hE>u+m1_7?$ zxCQ7mJbL^T>KmTJ>(=Y=;I3S~W}Y#7o*B^qJQ||Le`$eVCAeY6b*=9E;MBA74*$mFHpMA5!!y{CJ?vrVRr3&*|%-R{(a7l^U||{ z!y?(xQ92eBfv}I%3xdNnEI3^IDc;NmTB*pWSZ@8Mt>)*?UvPByuV24`rlw}Nd8>|A zl1ogR1f^x?p{)D@RA3-3UA_X>uHS;X+jrr?!^iOKSpzgQK8KgDUO{tnGjJULHDIw= zxOJPhGzWx50;211bi#y>baAoBIeLL#)KqjlU+AnW6gjGMnT;xo^)eLwFZb@yG4QPk z>^lbFIe^jm|DERuuh57{gt|e9Ol(Dl&c3JC8?7Srv7%s=hH0b5e~ixEFvLxO@`_4R zX<0ce!+bt`>^QDwBfP-?ylHBJ#o2k_6BGs;x9ot6mulcbhd*9pE6U#*oVl-QS-IN@qjCA_C_b&d;`HhGy>9o zoCc>#%g_;yhdq5ZdHPR)JirIx>b0A2tL_fmfA9#NKCS0h|HaE!d;s1a6olJ%?!qr8 zPg!>FJJ1}ToZf^ez;rN%X|y`V_5tO%wX30UAv%8BUX=rVV~W9N>IO(E*bg3))`2{# z*d+Td4a9A|mx=$=ojF=Z3T-?4Gbcjo8H2_%K(XnJ)VN0-D2k1D}Y=R(6 znK>KgELZ~b7iUB9@>Q_!z#&QjsK&gemAzD53yZSysFcxlaJlv>?Ad=1JpF_DYi`xu z<>7zt{zG_ztN*mV0h&!Fc=P5BY}>h;i;hnM3^0e9!9u@>-*HJ36*o6rYgDT>y5auX z@d15wS=Dk)0mwD^TuQ-y)3J&xma>`~P+ofziguj=rM?hkA(`ONEzE4!-WS9j{U;*)qrK?n@g`vFj$M}f4<49) zK6IoxDg8$il{R$9TwQb3Qi*I%IZfx50NmgmBUsN_uoyf8LSRWwK9b>WFeW4so5R_2 z75v{kK=?m-`V5^nL+#aTFmdV(bUcDqnnOK7jyGWd;vysUR_r>kv4-h_{!s{5UGY-6 zwg3?N&BeP)xofxY!@0{h;ats4*mk@U2E;A}8UDLO9&JYGgIy>8y$CG!DK4zIlb-;c z7Ap7>nJ>o_Fex>8qX_;~MU>IYn3Jv$5nzZpMa+vgZ{}-KD;d2)mi}q}A|Nzu-?f*| z>B_3haOLU^KBsxdkkwzle2t~5ady=a!7i-<0i8i%w|3oc+*XgK6x7GV-xs#PJ)JN`NM}# z;K`F`n8OY5>h)`$)r|?seA-Y6JcC9Eh#sGa0lA6aV}jlL_5+qw2vX}U<5eCo!NVmj zAZC(FxJDyaqu4ybMqctM-&(r?*wr~ z9yt5W1G^4>7Q2rAfVn>k&kRju|M6H7NkS4zeT{|RI)vvm2F~2XMa%Q(dwo%4|1YMD?!ux%Jw@(; z-fXn@C|WHM8gtbbj+SBoMy9UjZa;Ve_Z~ilJ9xf$?G6l#Edhn02qdoKTUNhwz;yf0 zfxPp?{1T6uj6{+W2@qF(vP7K=i?^IKtvFE5ArS%wroV$46{NmEMBNy~CZ_Phgq-=c zF2moB$unj{LP`do$5h9uj5Rl#enrZKsw$rOkvHHhu6`wQ#rByaCn!yl1!gg_}^kbIlsT5_5`G$@69Yjh`NRrlBpZ_%m;*|@MT-oK@ zTzz9B+<*KG2o0lCSA$GfXpuO@fcPt~W45EGy*IE&FtcrsdG9@ohqoLB(|2(te?jN# zMy9TU-KQ^cKbO~-{HJdOo1o0)pLi`~uH%;p`2Zl zJ9sX*e)ASOZQ;}8R|SXXS@Rb1B)D|N8eRrtKfo)&r1VK#kXmO^VM$v(Ycy2k*u#bO zLD2l>F?VKX4on!?vuRR5Z&{L?BTMy=3e(3ad2Qb(cB#NQx)@0g6Om_e8Z!-SJ{{}b^Zj8=GAQK(1+u(ClZ&M<{{tRHhC&0vAEvE1 zWIj=O6^>S1;ijxO1P-BD4Flb#wY48UnW{h(PfXWPt2bgDrxm7JO*NfhPRHS6zwiR5 zyyC*I!o|{>A}iOf=hfe|pXUAQJi?xvI&+R$ZHVR6T0M;S9n5W;B!lyXz2S7G9PU+a zfL9H-OpA@fVf^r(t30gVW9eQ>VTzjr8xxko6sjzdJT#Lzhi4DQ5(eMJFXL|9dy2&T z2A(y%fa`>U^feZ7XcqKVWlm=zPsTb%2!_TLGbe4n0NI}Pi7sl7CGXn6xTUbawAykU z13*D2*i~vq#R!tYsd|T@6B%iMyA*_zr%O2^HDdNhC|QZUA0yGddD9F>Q1bd=07}cw zwGbuNayqbjj!;8a#Gt~5&u zRU5;g>E&InbXPV^7-hpv^mTPj8iy!wmkJHOLzv{R6WPF3rv!tSpJdJf3%1zdD#m5+ zL)clM;l(Qq1XiA_cfo7&dd?OR;uw*08Xfi?J8?~$VaY4n_KPVV)5}m;(K93q(u)pQ zDzDbTFBh&rY4r_Qy7#OZNfsmnl6MTjl1XK7X#nQ$^406i4<9|@OB^XT((Yu$@LRU; zLLv<3%iY^!+s<7UypAK{BZo1KaK&Rdnh#)5P5AkCm0BlB2pWQ`E6+?ED}y!3PUecD zUQn9t4EHZ>g%?k2%u8ZM5DGR7Yr|NEmmEH6qtBNXl|<3=GT*r~DsdxMvFeo1LV??Zpu!j+?Lpz?X<(sii3RtHNq zpWq}I0BP^|$6cBA7o-G75m^z*q2S@AE2+F2H|uyKM9f>9#S>aRB7|~(>B`k6B0Y4p z5}jU7C6B5J2I4h3zC27DrHD>UVex^(MAL?R!ZJJ^yQR3v5Cw8XL2s@!Qv#Q^8Q^u} zO(@-!35la@VWN+7|KwH_`1`l?|4P3_BH7R>%-&j+Pq zYDRLbH?RJ=!jhD~AEFoUg0#W|mX-UqVLn{5ho`bTdk!2}Oy)|DkQdEg=|nfp%+b5`Z7$pb85T#uk7 zV9eYD_Z~lQ(JEMtULZF#BVoUM`HFki(8v*Y5trS%_Xw`uegKzHJk{L13zdk35h<%U z8J0x{8@0vevvJ>}4v1oS;{(t*Y>7E+a!h6CDmaw9+~t~U+`L6u9Im!$`s{fY(hF42 z)lo5pDy`uYwZX`+aT5B6hFf7jAP81PVlZQbhyYPYq@GRjbYv;+5@9m3e43}D4Wi%` zq2MH;AWH(5cPGHh`dT=%V<9AtwuN*b=kEvwY3>dpBr_|T@?cdMe%1&NkiU4t()k!G++9TS#KHq0(HC(_@3|zP znovAB^HY}yeKZ9@M`T8-a6EQ-v*s>@sk7$r>W;cV>J>sYh6;^w{76H567Q%=6D5p@ zG72=&F#-&R2pty&L}=Ks;hy{`E!kbpl91UmJmsQ^o(e)i?pqX`FK~vk!tdZg?Ou3R zcig<#9;{S6Q~VdBbk|lBD78D{KIf<)W&eV(K{+R`F&$b3JW*TsLCKv zp@db93?VHc2vr*}m^9)xu%SZ@d=49icEg-jFeJ7X6nIGN5d}@tcrIwE2v-kHgO^V# zxg)D4Lc&NJOGZFnXFvZy<{Y`i(M4ZyPpZy?A&Dz28;(}Mp3-XAa=Z$5ov!9>>Ao^h z^YS(es8YBfDh4O72KVWk!SAPSFlO>b7%*WaNFwvWE@&~xM@;7IyM{Jl+YG20cgXEE zH2Kx@hFV9FRIGA`CpQigADCvU`a{ZyZaLp1 zooa{9U&lHQeT~KD<@?XU!SWi|aq1%MJaq}Sowx{FPgG$*E`w_J7G4%Bf0zUgQUhn- zFNqTmN;QcGrKgB3)8s&n6ARTE*tp;jHaakX zh3lh4YC{~tITp)f91GK;lentts5lxpezqdVvZBsI-Ly-_fEt9yl99T zZWsvc9Rh^>O$LPO*`y`u?0aP>ZG4c?P7R;qNR0eO#loe_S$xJs=H(mscTbkn#>rTU zhlGEf;EnG+q(?9nVD}7HOL?voDmO*Ki#tc*?BQiFC~`K)b;a1Q|=FlXH{eiq=Q&H?)#s+M6$=fDNxE`dYDodeRaJ)Vb6QZ8DyxKqFs zbUeVez3KDC;zu5wC&(0wC@tg$o^?ZvYQl9AQdFLquBA3`~}nDZmOpO zPRy5a`?H3^@VG@NbP7Qdk&VsEVK{sF20B~I+aYxR00!dl`C8bDBse5tImpm?`LG|k zmPt^+GjV%AdvSYThNbO&yN-TV4xIz6utgR*bP5t-aIEliEBtq%E#|#VSARxrv-nG| ze_jBR#yGM>Duo_0QJM!<22bUp^s#m@+xWd{MEqiKh%5mq!uiK#2l9lSKuf* zeF%eaq`c+qGkHCt1flOckC@!d4?{Zosal31ej>>?{;b=l-mJ%$-i%1jroA`o_PGbM zZWG9C+64aVw%KSl_gVBAmgJ!n?j6Y3f>EE^NBInG?iINNq}mdWoS(MxFjQQ<#dmFF z)WJ~@j$jZ@A{XcuY$rJk&Xo7vTl2n?f1c#)V1_F$>edc)P|2fdX(AySUVX{Xo6F z3qs#f7WH-rdfK{;+n>9BIh@b??rpu8O|AZ&j&hr`0Y;J9Fs-B@K68y-imP0JS7z|}j$IsHQ@|T_bbRh>a@*rIhsk;Dg}%u&KNE5Gt?U z=5xOa&lCjO_^7^l2e$oE1^tXDh!8DKL5sldn_v^kSaiPFp=$`E64$ehH-FF8ZQfBi zvkl8*PMEJw6a;k88KH~DoPYDM0Xo4(Cahv?)MOeti)iPiY?*$^- zc|oWvoplA*v8AQ%%@iiogu45W_)U(>H}3NNI$1?+9pAT8a0ZWGhSi|}oWrw8 z=~?Vh+9Km0wfWrrBkPYxGgNf~Ta>eSes{2UYIPP7Am|&rRPgQCABE`95OjJTI&N-yb`F1%@S22bTvf+I(=d*I2}DeZi(fV3l2auMXn&UgWe8 ziI3TKc!$m_5USlj^TnIa5!i*yWs-=@k8$PJ)6Lt5=Wx`S5qT-J`5e9n?9p%FzfJES zUEHZ}%u$_b2d?pV?ae`1r$7x^A zlbpx*rovsNT+OY!$j~>jHeYKIC{raG=jU^8 zgK!IZ;Ng>JP;uopj3eh&=zPplkoHdk5<1px10a8r9-f>@hcm0xrZjgcq`J#y(AYfP zQ)ZPsR?33zb?~(EArX&*FIGU-a7yWE`@c1cTqWJQJ4a(`?$Wv-lcb&e-_viaD zAWOEMv>@Eg*1?%vdeK4JKZ0kCFZcoFlln$@^t1sUA_tJulnPJk8+hkOBkX^5eqahe z#OU585N3rBf!q7Tp?2>CxVA6WGC#5pB#v>sSd=p8{Ui?uLV+N|TmJ4E6)aOC>yS(o zF?sp4om6%0u8E*ZIa+nK4rq7f5|S0A0Ci|5Tbq?VXD;#MDMSJk>?wu8@ym!eU_0hL z&Fl#UPaDuM2#=pNAOfDj^XD(HADgz~3luBQm+dWwH?LoF^iaa1r_bO}SuKoC zT?Jw!JE^Xa1PbS%D>OT53(OuruH#l8Ea!u;=~yLf$CQ}6;W*fZWrN?eP0;w_6+f#d zBs4cS!NGG^p?^#X$zh)GBm9Fm1HDHwi8ImQHI!T`ioLw7lGirM1@R>?AJ-|*!v%={Ca`TyN3_y-ERIVNa z)mua1`tDHJICUsDVYDMm^pXF-r-8dv6ys+9E&`A*MCg-jwZRhBbn0RqaAp%Xf8BAA z($tk0e&|FF8xg)0MSEoXg~x7jd%uIq5tAE8xcE=s$kC<+jjT5xuY~0X%AtR331HKM zRiFV@AH0B?AqNx~0Gi)By6{hTSg@z(>^u7E{re4KNn>UG(>!EAc3`#(cP`Hv%8LiX zrOg3Qvo#P-6^}EedGOU>Q8ES~-Ag7)!D{dhQXpT@(R)7o+IPWMy`zd=9Iw0uX$1#3 zTkOG|upFW&X}MYI#5x5O*meu~sv~Q|N`?dL_NV@Bog;FNP!LiJ_giRqx$)Qq*nz1K zLt|@X_<-q~`IVQ`gGy+QO5@v}z6sk}pDv$=GTNz58YBG-OWY$Gn66KA;w}`qLTPSq zs9NU%7dLpLd0OU%J44b~NzG!d^9M9$OLdb9e*B}=fC6A0w1j;=cJ_Z@8N5G&*jZD+ zIR!4FO~lrzYrso>_SQLw=IH{NSR+tI@mb zJ7fe4}BWn8LcxKA3-d1BB{RGoJ2U3mSpEyPag)vI=`9cgpjtg8^IRYxz zx7H_y<|&<>j65(;B{!YTB_42r@{;f1 z!kRH~Y29czlKH(UwM7(7N50@iQR3J?SP7)z*!TM{u)@}pDZ{hp$_Ay-j>?vw?4aty-VQNu*88ke@c2XoO9>H-rTJ!X4m1&+`pV zvxvX&1iQ|GH*MSdINEojr@8&D{=|CY9oSP{w?dDmbgM$9+t!WzEH7Z z7$RXfR4o6&GAmdK$>XHw^Nb3sq_OryhJwUD$^~{kR6@I+5zNj`(@osg=WI*9`={`2 zvdk#JTov_Kq%qe%=}bB(nf2v|o%BFb!TZ$rV;`_Yf)&Ac+6F!VJ5RT4Ut!|}5^W); zaGymJotMZVwy5)IerMIchnDq2`@wOq>b4B@1wV&m^TNT2!ZXF9!%Uhpe^>>eH_0IMu-OTJ1OG! z2v@O|bz66pCOrIrlb+w_|F;HK4@AIIBEpC{TREEPlT0Q;oVof4J<`T0{pa&Mg3fob z`D84!ZRaace4}RF+Pbrd5$zb3H9<1!e6%kR)(y0czBZMJuouq*<;4S`eChXaan%sm zGrPYj#Z8LULYj&N^7E}#huzqQn20yI$%v6{&vvX~+~i+3FRet`>ij!ydA4|_!w z2BF-okZba2+QZSHB&A?qYp>AKEFD86OkQ>n9BDHnA_qw|2r#|$NQ69}{Py>e&5;Nd z!gT&Y3Rwy5ATLzQO=Y<)DR4Hg7nBwBRb-_IvNQzo8oaElYEkS)mc6Q(6_Fi4m@c_&r^{<+?$VvvuWZWhyEv!lO8+|KuR2;fnG9UfHz41f=j;6yK(<=8?OIG5iV zfL1$)0m08r6TM}W21QBZ6l|iGl79;2-EC~R&!UnjRA7%F_nAA@31zHYUue-T+|_id z>ZZBr&1NL=n~E)x|oxD{A9NOCZwr#Q)8h+ z=K$KtrRws#-y|ka9_N5fk^Kkg*cA#uu0dfT$KUe6FN<90!3yjLWX-%@@Q`jz5AMw} z!UnK3w|9sD(gTverZamxEQbzSCL1$Nq!_YLppeXFj(-^?mUa)#lq#a1$-YYk*~qD! z!gDsDECwf^xu6&~17w5Kz)@mo`40^pc;^pUf93UQcPxQ6xcUwqe?QZwF;_vT7n|nO z7kNOommExSmzj|aVTo1&^TQQ9FC>kVnK3{pmZTSx-6idlTdTl7qypGkhcU^Bsm!i- zBD0qogvz05LKilk*|AX8ow>BN`SWOBENyw{WEDXi?Uh|S-`C==yk20=-^KR_(fQ~e zZGHZuQyUNdnST5B0nD!b@1M^hHphN|bN~e)r?oXUQAHp$5af{@Io^2>>2^$)b}8fd zj!>BDCdaevA5#Xqu>K5dIP1#f>|550iMxghCC+godhhVzP}c8*VXXUS?(d<%X(w*) zV}}x2YTGTeo2|9_lO8q!0&#b0qWn=#`wHw@2=ITqlRI*VHB|s%8U_M`;esT1m4cA& z(c(0o&G`Okiigz8Ft(F$mhWJC_)3^YPoa6xe!<^A9QiLAZu~udrJ*bU001R)MObuX zVRU6WV{&C-bY%cCFflMKFg7hQGgL7*IxsmpF*YkOGdeIZ=xu$&0000bbVXQnWMOn= zI&E)cX=Zr(O1YvSJBDY?#US%vZm&yMO5WIghz>_AD_oewSt8IEwD3REO#~ zr8(7nQe&d=qRKp?_j z;s*G*6B^5*fGgKui4ChZAYGS6+>68WJa~==B0}H?@O>Xa5MW|z0=s*6+Qi@FK^!6XUi%(ceAhS6xficxU%})1f4Z^o)yi{=0&wqi*NBRG+_hufUpfD) z=xp!AXn7RJQ!Vip3=Iw8`%ixdMV%iMp18blkN+x+;(zCUi!HwM3oHKw=dbzX_m8nZx4$S{e3^+p0m*l4 zT6GOhU3MCzkhuHByTF1!le?w#tcCYc@>X|~DDbv*%htqN{MlgymZu~Ny z-t|=c`Ge0rL^kM;R$PUZZL2VzA4l3tLoz`Ne*fn{{mssLD$;|ebgq$q`1_y3aB&zP znFw>#Kh=-()@;BTC!9%A+JFO06ea)yDt-l02nYf#2#~xv*-UxyL}Q{Uap5_aV5BgD z#Y!lF{=t5Xj*miz8qzc&1zgWXJP}7%M;AhbbAuC((r|tlU;ts{su(DnCP82l3j^4t z@ZUw?v|Vw*W=!}K_#o9ebr>2L!rr&{V()>yDEs9FM>-Kp;G|_Iq5H&cG`BP_2oQHI0ArSn-mzXBdf^bxIsF_gZ(F`#_{h3i1q(vemV~^M#Kb01^Z(1dakwS$#)y!&1cR6Yv8c z^U0Pr_Pw=lPIw0r@xTTtgw_)4F?jqGl6Y$52l*iwExj(;%!)6DkBj^^KwPBQ}+qg$NC_&;iE;lBVmqDE}R|!^#HE@GQf&B;e&pzznf*7H!w=U!R79YO-kJH=A z!{O%QSw6}agi$aG2+dYAI=l|J+Jy=gl&@xILC*k$h{EfN21AP)Kz^k0L{%xtQ0VB; zF`p)LlTcdCZd{g-Df#mQ_l>=YMXL3x)C1+AaC2#6fXWpiND~@GBick8T;&24M28=Q zbJu;bSVV3jhpFsT0rI6h-~zA#84xrxSmS^w#U!c0Cb?bSZjo)DPQ9#Nx+vr;2^TB2tXn~@S~}H zSfu`U=jhk6edWK8yCt31kDX3Bmz{OD}51 zT|IYAOwBGKKrsWkTo%H%qA8R@f+RsY5^S1wG+;*nrZTh-fe)eo{F_&|05CqjiU64S zATuL-IE&EMsL3&Q0pW?L!kKY~pr<&#^v1e%LtFB%;~15?*ptEn;E;wO*f#O#!-d5I zXo(&sh6RBT`pEE+wXNp>kjvnHMi_(}0UQ%R=IS`93GHb?nzhzU;J~~*h zN1F0bRA3bgC9`?2lGmKt)@A5VA8h}6 z{W5%jI&RN6%~#dkD6{e_ZJ$nc7rOluY$>`dDcTK0tDFWQ(IgFsFd%{`4F^f%Iw!E= zS|Y~KdfqR0olE5YjznrjZ+i~fV<1)?d2$d1u9|9n%k*8#LYJLJuBU?HWWXHbdxcH^ zs$}?9@7E_wFj{$cN|vTXo6hpaa7f>ts&l#i^mu->HJ?|Svs&w1$ULc(o>E#*g?hSj zL9sTU>!RtG>g`xr|DP@ul%K6^RfF>beBQcF4A8%{u20}VGFJugPz!AtOEQ*Z`qEe! zS3hfxC6JhOkUdmJeyRcv2mlWwKv|V=f(|s-#q3Hk6O@|P*A?@kQqasrWx`KGm^vAn zegvSReqI@e24osyg=#3ivwOTqN$l}|s7mu*aQKge56Vl$opMy3;xtJ3mYQ8QQH}OM zEx09TpiDm(ni)ujNT24Sl>+qk3+NoTaL90D0wO`w&>Zuo;LCuOuvrKb+#(_nX=5BQ zr~sR43<*uGi4;b56~GRc!xk>R?+uMk@~!U8z zq9RC(o$#!0h*3DcX4Fa%%pA0;?$(ay@f0|ffx82 zjQ0iLM|nG4LEFdn6)x;Ps{wtKt3e#S2O`hTgt3qWWihm+p(TR}(~d+M(*O{PqzpvD zQc@CWfvT9Re>jp5JWa7W)`T6e_JLiz!}oD^ad%`6t{g5D_MH4{Z+Xipb(o+MgrGUg zX%=J_1{NW3G?Z;@aTP#RA?Q5-MG&EYmMkn^#H>Jw_YRHG$TMrrLQ7Luq?`1cFCAtL zE`jmlqA&S)8|Ue2`s}{Gv6nyD)R|GOj4}mLXc2iPJkgx+p{o!YfY9a$A_AB;RU?WB zKo|f9`vMG*4dF=xQ4LPII`XW$6H9dO?r|QQEC!I+##>ln9tkWU{x{x_1U~<(J$=FQ z!x^+%bs(bJW{xaG4Qe`YXdD5!Dstc{_%jY-(^W_Bg|3dQ98--U;Y1HEK`B9$JSDXx z)w^>d`R<{t2Ma&qtvvKwU-FrmxDdc2>rQTI%&hXzt8%cVFqShGfP*Ml;A-QUfHO-; zTSv@*BFIp{^2u1jstDL+3ENbwEEF@9PIT&XFYh>%Juoma0SmYB{rt)?-%`>SXc;tq z-qn^_d)gUA^vfocIjjMMo`xS&HHdJ!&Wx_M8Co!i0TB|cK$zeXb~U_lwRTd5y~DEd zmdnTW#~#1S`hPm0IZyf2jS>I=002ov JPDHLkV1mXr+o%8l literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/save-as.png b/code/nel/tools/3d/object_viewer_qt/src/images/save-as.png new file mode 100644 index 0000000000000000000000000000000000000000..ed2453d9f427bc75170a223ff476351f39e407cd GIT binary patch literal 3792 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/save.png b/code/nel/tools/3d/object_viewer_qt/src/images/save.png new file mode 100644 index 0000000000000000000000000000000000000000..cc380a03aee7f4add3e6b6b78338ff6e031c9688 GIT binary patch literal 2122 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 V^WP{4oRkdg00002b3#c}2nbc| zMg#x=010qNS#tmY19kua19ky@)q>0b000?uMObuGZ)S9NVRB^vcXxL#X>MzCV_|S* zE^l&Yo9;Xs000C-NklxciUL8 zXrPv^rKf(j0gwW)_DEbRD~*Y%L-zZKfvyg+Mq|gjt{WKYscxqu>+b<6z)$wZ^|CUx zX^+S3vrAu+B!nz$ZQF3k2Ocm`-A;#oz5yxl!$Djvt5e$!I%KZ}9kp7L1d3v=F|+Ay z*DV^TuBFF*Ii-TUR#s>3IcU{GmJIau_4M@&^bPbaShQ%jM}5{kGi5ceK?*+@#jSR@ zW!YEkGtkvht0W0;dQ=FZHP_g&>3N%$bSCN3zZzHM)9r9->aZ09JzX8OTBTBX%T={n zt*ftZ!IGy(n)-u&d12n)^mzyE*3;4Tu8&I+c*Eb#?Uy7;0t5U*SK9yQUsvVv zyxizIV2@g@mb~L{0CsG<;15VDwXU9?&*^I9lu_1tKv$9l zlBsPI7bRabGqt7F_kymcCACWJV_I1nb;_v7mUL7q3B2vD@Ss<8H8$Nb_kfNIdY+V2 zDtlx_8FfMkBnb(8=s)h-XX>7RdEfhbdRhe}BuNONig8G`gg}5w$Aa3|9FtVKYDse3 zmkThzELqg^Eyoc;QQVP)GOpq#wSSw-iY$ahyZp$v zLC=C^OTOpp2q7!7n`*?(Dqan>TV}FW7Q(XcI0;y=XxS+zK?qqZyP+21)hb?hzqaGL ziAJkcobXiuLrcEvBnU;T)p*N9&HZ@2dT|!#Dt(u2Xf)=sBLcwCH+=(yBAaXc&nx;W zaqh)gMVyIAjq^55H5#p~2=Hac5sIwUXiWV^sKq4CQ1Q~(*;?O!UA1M$%*kike3;rrOtjMyCS}jRJ$g);r>asstP;<>XqyW$Ejq9<} znD~U}tmx_Ks8paRT8%rNcgs*ux}6T4-+&Z=XCH}6vC^6;j{1V3TCI|VBFoWeSFM&N&ZhMXQvgx`0FIC1v=gx$0)d1iN%x&gXI>ry04V?faJ(N+IGPT`$Oz*! ij+kdg00002b3#c}2nbc| zMg#x=010qNS#tmY19kua19ky@)q>0b000?uMObuGZ)S9NVRB^vcXxL#X>MzCV_|S* zE^l&Yo9;Xs000C!Nkl1v7XOj6zCec48xRV% z6+x@wzAgky7ox;eLr_FZR^m^(60B_%v0Y@LwzNfr7&oPsAf&ozWhz12WRl7J+ z50?-f>GyCZ=gfTX``vSL`HnL>VEQa2`x^D1l-p=@8JvtAn`Tzy8WP}q2 zc+sXsQw4rtE^H;5b=%%;Km?GC`Qau@k`Hq`z2wP>?l3eDqdc#T80C$Ch;G86un@gau-*@g+~O%(O-?*}A*uP&I;NJT5k9FiJ)8?$kgQ>tq5K zc%PpsNcuDl5f_tja4+XFZ_Omf+e5&1hS%1J9tDNQ({dCpbty#a_ zms+Yi$fi)IspJ*?-55V{m_^!nx#WGY;e$b>af?(5t}FwuNiTYQ;rNALS>j%u_l#t1QP=uU zmPyEm3>PvtY3=dltRVm=&@ftDmSK}WRDDV4GoacMk>)8g5Lwl5tfV#;(n=Z+@g_s- z$q_pLP@gpKI>DkE`F{+wjPb(48TXL`+3e#0o5MGGdPQC)CcIb-2EH2EJY@KdcGy#* zl!By12rN22ar=9Wqhqg~0kXN`{35E2Hbo@8I z=dcP*YgBoIF6Ft8j=U6sO;}xS9A%jXEfI%(+6J-V8Q~C*(o)VR7#XZ?6A;1YRi-lX zyUdyJ&NqdQG~f~GysfHx_4{{RA}0f;q$CT#B%_((0^i~oO$8g8+Im-1b;-Y?faJ4G zZfL4|)l@C?)qMDTc`j4002ovPDHLkV1mmrHkdg00002b3#c}2nbc| zMg#x=010qNS#tmY19kua19ky@)q>0b000?uMObuGZ)S9NVRB^vcXxL#X>MzCV_|S* zE^l&Yo9;Xs000CyNklF*k_h zeSj(z(62g|sNwaiB$%6W2R@iz2N=1}PWyp2O#z0un;uHxOh{=^S_R4Y-U2=QJ zFRTH)*JsmyAZC$Xc5*A5DB^;^NTozh<~hw(x`h#9=%e`+z&j;7=Z9sT2N7?L0=GET3SBm^*l!GXGs~(O^k?pX&1lXE{R; zg}S>r^1IS%Tl7JC!#OQtrTot4Tv!9}Tw`7;&>=mnzira0-xa0%Qx$CuFD_4{?KmK= z@Fic-SOK(kZbQmT9hBvtekN_v4@U)eyRY8__oq(w2eE?i1!Al~lxCEF!JJ`ZOb^wHD->opoOP2${*CLxm+<#;+#Vd6C zsZfNWsmN%m;!%jwa zmU@mQrfJe9QA&j_p5*wNasjkkRU}fIUug8=(?b|JtZ8E)z;`t!aw1;j z91WhLKnp$6Adi%NSErVibz_K!+4AHvnEzIhQ%iT4A*DFaJ8yokO5FgVYnXKV7RVAing15ZqTyv?rJv6DR<^ri*@T? XW>@+Kxuv$c00000NkvXXu0mjfp&%!d literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/sound.png b/code/nel/tools/3d/object_viewer_qt/src/images/sound.png new file mode 100644 index 0000000000000000000000000000000000000000..2862fa399483ef1abf2a2357a5d8a50bdf1ba21f GIT binary patch literal 6344 zcmY*;Wl+=))b($dT|zpfyA%{yN*WeeQV^C7fklu;8U%?I(WNB>L^>pt20^5jMjE7~ zq&tMA^YQ<_AD(&U&bepi%$%7!ckYKf_eSVGQ6s&>cn1IgQVn&4-am%_2SWUR`HQ2+ z{6E5f_E-%8-24~Y^kbp_6e4$ZQ!fA@ru+|}5L>JG|3qJRMf-mY|9{xRouL4L?mz>f zXz+4+*POuH@Okb0dODM?nu-Ah^zP?(x$5z;a4N$5)A?7RQDvn7at*>9iFp3!2s1^Kepzl>o$W_bz)e{Fsec-b-{-ds0t*7Hd`JF0rZa(!)WKCPkUdbg?N`Zgqd z@2Ovz%w)#)ppSEzo^dX}M6PD#CSvthIREs1%hp#g*~BH~HwdNSD~j>+1Ts+&#Kw_k zlu0Qu$SHuVLSikvphISq1)J-2@y zr5rwre+|yi>7>gNjVK~Cg1tTx0bmNTMU+n@ARE_CO~s5@$fQ5@z%hq5eVj<#{Yp0E zQ?^kNOd`?#B(W8HFVmZ$3m#$a7Xb3c1cpinD=xp6iNNz4KacaX$-coKUF45O63a(y zsjyHVTqrf}VNNGGES!SIE%4q%-x-T`5~?72w37nBPjCp+icmWBA;lxlZr%N=;-gGS zQj7~Rm)1l9DrRx2Fh|GX6cKt-zpU|?5pE_s%3eS&mn!_>%fXEzblH$Gl@YA*21&i8 z9>4Co6G-KdE=NT|uRM*h^2z%UJO+BgT>i1lS2~%DKQs`s_~hv*xV$g zP+?6@J_l4Q0Zep#pOO#zS@4L*Wa-}9YT-C7T)T!jg$B3Leh`!N{-xwdw0#`-`+6*= z{^HA|mgDnfF+s2K@;vCF{raz|fa2L{Dy^roZ{<0+R@nEU#5FW(qXlW5ezNr#RLsrWudJyW-$(7s4`ncG=(k2v>*C}1c5Sd6$+$Qi-_tJhKCcaP8~*j_4n z<)V&vvvUn;J~?)A_M-5JC$?Y@=!rYU_qpe)Vr+1|h`=N#)Vgs{T4u$>TE8IUQ9YXM zC*{RttOA_LN2?l$zJJZDi@^PKm!~<>X3;XEnWp!wf4t^?o%rFVquWTQcF%<#s}8-{ zCg8iePC1`YRRF<}!jzwkcXGLG0!mAw@nD;5rOa0D|v z&rWS~9?i_sFn%EPuT>UN~Uf3Wn+WG?j$j6fEpeVKUxbbrh9T(3Tj$Hrzkgncn zr2=qIPE~(cP##eJE?L7rPa{h_T5R|aw-|;Lx~c#@W{73$ZtCNdY!ZuzUIi3Or1of~ z$hGq>f-0+X8~N~-c$~k}Cf<;E>=7oe?(Y|f;sWQN&L*yFao@~zZw79b|4wEk2Vg2P z9uC4I$GH6G?9T(U5TG4xzwQ&X;79d;yf7u&h zma4fIZ>Q62jRV6!3bLTOyvBsK{jbxw5w|Ok%N{iw^j*11xq$!`FcuSWHISt*5Z8)A z2S2rSKc^8HfbXh@B+uH(snN9-%s#ES!`XxNVq_LyfN>nY7t|>bg;^$RxkgcMwyL-X zYJfM@-`@rz`;~5~;u92}aF>*21g0KcRbDhh)RsscbTj(nx_8V2A0S zl+`iq(#&+Elq7fkNxz}v@qH334fkv%mt9nYpG5;k$2AEL;Lv2kW{jc5Rt)=ZN|A$(YB(lP$8VLm@J2&Xh0!``WDNM*%=1 zr@r1;X-gA+!lu)FHtP(gqSpUoTuJ@;-8I*&6^tq!4=76$y@bBRDMNfi1B-l{`jCH zI;ebdX#OqMs5!140Tkk+q z1{(lkP7$9?Hp}RZZ$!?9Q-(s!SCt=PkoZcDG{V@l3A;u4#H}!B_*F!@$Cz+!3uAsw z7dlI{5ryIlcwNJ9eD`%vYWv1Gw^|hsIkzgPiV33aV7ye+?dbhf=<{UXZ~;A1B1O`< zHpwHy#ftR^b;C}E#Qbu@fWvfaS%|FVSq7P^S=T((7u|IH53EkK3;`OQ5kEp8iS@pB z5l_6FMj8{EXgJL~Cy)z2DBwiCbJYQ~8L#P| z!BDi8LJwiE7g0hIQ@y-#yrnLKw#yPw>P;Pg8*fXd+i?HBe%3XX>dQ~$lVp!3pIi=% z$Ek%cXHEny>*6J_93MMw9LwAZu)OZJ7E0R<SO(=t4= z884mZ>WbO+;MCzwt&gdgXNDZ~=$m^pD0ai2O?ifW904qJT(#QC(?$4V z!ifQUcQ@i^xZsdl<|CVJ#gH+uf@NYb8y9~d190WBF6l+&4Nq;j@vPqnKNwR?ZvR^! zBbvq4m2&G4%q<7*rGCdwAgPMuDhd|PhcVBw*9*D2jcer|`)`nWg?}UH>KpmLZ*A>XWo!BC|Y!}QSNIBgGwazC8JcOFMf$;*n*=o zt1ZET0lOy)?|S-kQkkrcRdtkvPR>&r`@ug0P*W)tgBA(x1F6u}m!jT%j?hi5j!a zPr7|+C^O6y&E8JNOaPLWO9*L_T{RgrXAfK1Cln?AN1o98&&pSi#W4yX{3FrcXRjUr z$x%x(^J2sO_U%*CYiTTKASLg9mcO3Wg7!4EX>=?!;uu6MeW6!afpd`^4XdxLvV(Ks8Ga_M~z4!}? zYYf5=lN1cFqqTTgdvLJPk#-{ZaZ)IT^3;YzhQ%xnAD6dbsf7-CM`E`Uz%~XB;Cpkl zYiNsgFo%}@F?#Y*nh(DuyY#c|MYvlHVRLp3`d@{Es>;%O3veDz;_jB2Rj49$N2ynq zxZ0^6;G(fnPCraDi8(_hu>d&heP3XQq=MxDA5WGBFh2QySOp>2{N;iVgiMJ|J&g!8 zVR9KWR{TB&NhoG623l>xW7Y1Q5dnSrj_cD};(MA$_u#jp_}N}n)!bT&gWFWDl2_PT z%?mbquEXnT=g|3`S9zN5A6Ni-?evBCAE?C}zv9?_H_&>1QZ<|`t?9Es2=nydqEx$xYsbBB_HTJ2lq-M2MERazY4cZLK2BajT0%E68|WR z%OBO3TxBnjTS9^4g5h@eGf`d3%vY>_l=5A%{G)ybUme^+41uET)R_knf2WQ#JBKWO zGu(0k8rAQp-G=;U`_;hbqff|dcTShRqEtqkmG8>O5%eIprTzmx2w+OTrA6*@#A9$5wOM%ZE8WjsS}=ZJ zkx+X@Lf20t<87Gh^CdF!=GyL*At46W_%B&7xJ*Wo6B-Fs>}ZI(%r?1r@M9+S@@hl2 zP6@(tV!-zo4;Wbd1YQ{1x><7qvG;$}XDQKx?~*nm3ynU;=CSl980W9Ze{`R`cq-k* zL4H?0B^0^(`I?~xdR=E&{A@D1n}i~{`A(rVJ~l`DvqB=seO?#PdNhk5D%0M1wpiDS zX2}fnsbdRibSk+UzYbB`Z8Q_}80&imV3`v5O950Cf%*F>mi0%QkogVBeRRVvi3zjo z5|k2HXlXoO=?(S;lwxG9fl(~z&0SL7QEVvn{m}JCv?ER1xTNYF-5`=zbVvK&8C56+ zbP1RU=N2?1FLxh+;e4d}o#iAMIfuLxV9)OO_`*k`F+Zy_37~nbHBV=j9WMiM1~bOx zKYkAiF+A5`UcOhiWGUN zm=D87nWvndb3jE5NeQcdwe(t)g1o<+ zyQ^X7N=G;l7ZqFc2&2V>*j&{Ir`rwRKD_hCLg?9z8q?T}p>=suv9||~dF(4c<9WlU zlMOBbqv!Me$tSq{PV#$g=w%9p$P?}yYJ_*;zEZ=;^ZwfulvVd>!T$24J|3Fg1WDzj zlv@49U-55H?f3!t@8&6ChIR@uG#IM}Ldiq$8#hB2yB1VNr1h{-f|Y{7mX%EE*eOl`e;(-hSx337N*PBcf{_d9N-2{kjkGs*^?C#iA<}8u>7Tg)>{V z3tD9o`o#}e>L1X!85`_&ENYNjwD`sl(3-j2Hu-H#et8lRh6_~ z(Zyv3(e{!KGU{!>HVjIK^vC{8{s4EDMvKv?;DI&5M%>XT(0;*{JTVz2kI{Hl9GBB}&NE)YvAZU@2jpc&&+6{Ti5IZ4fO*=Xs@w#1u zm`y9d!FaU&M||dRTFv)D^{8D9Gci@`5fDtkCwrP^1lRRNP(;`*+U{+7ZO{)_FBgUN-W(r8^Ba`gei8CDL)I@3`-`F&mgutO(dvoZhPeR9Zd&SsrvVcw0tV zbt-CN9N$`XE1~eTl*}izn$EWHr6Tv|hvB;R_#~UR+tqHZ&8S{oA3$?=>%{}{nz-(s z4>FoEW-y}N%94h+LpikRAfU8t7ED?@H45c&i8pv!X-!R!&$E&r@vHU z@h*3nnCacrSsv=98<`LP*!3V$iy!Ly!M9QjC1}Fz_Pq}AS{r%N6l~yQmSmT8LxChs z%zjriBO%Y_2yqB$;t6%pRunUOSKv>d3DfK=3fwLO?P79m>52mxQ&*m%Dif0}Hx*~8 zoT{EWdaXVv)u*?bAj3-e&C9P_6Z0xqQn_iV%_Le_DDN#@Y5qRZL~2p2^R1t=4G}a6 z+*A4cGP8tU0`uEc^ww9BBa~J>Rh__eF=Jc#VxBj{sp;FFzN*RnD7MO{@_YT^jOCi1 zDEaNK^19$H%-^9f0`Z-seG7=(8tOTjmCr2^pl7n(__GM7hxuw%a$vy3O8oqu zX=cdBl^VXSfv}lfH20m{z)i}kZ>y@Rjsops+Lbb@dF&WpasM#+Dy3XOcn&i^Fv zjWIYLymrA%zDg~-wcdpH9d5jG^)$+!wP53WY|T@4d;2I^lHc6odtX8E!<1s1>bm3S zf2`m~89(oP7%99bpl%k77x1vJGGrHsV@*E=%cUqFceOTeFK6RdLSir%EnpTOtbue= z%}I=bCbL%>9);-ajY|A!0yyBEt{>Qk&nst4Q(`x63|iCwt6KG!h3WFxCe`lk!{KQC z!%wy`oYaSnB=O~;d+&D-h^2%rC2Ox0lxGcuKiqfv#(DzJ(>+l^H1vc-Vrz;xjkdg00002b3#c}2nbc| zMg#x=010qNS#tmY19kua19ky@)q>0b000?uMObuGZ)S9NVRB^vcXxL#X>MzCV_|S* zE^l&Yo9;Xs000CGNklrkmC6Jy9WesR^^K8P&yuG_6BOq91w;O z0FIbYMal#}U|XkRx6FwT6Tku#*>#;1HyPf+y_n^2g($1BtyaSfaR;8k6ZA~sI(gSC zc%vyhSu{W$FX5Pt^Vi%%4psu)qlr?gpwL-XKAv1R_HEJ~1I0WF4?Jpm~W5GYEj ztH#jhkPumvF9GJYitJ75H}MpnMT6lX9Y15XR54~N2ULy4d;0$f``E+=_o>u3cZcqz ztB)Df#=OsD0F^qVjg&%)$pL|--KDEznj~;+%&Pn%Kky?%Ctk&nW;j|!4e%6}nAfZ3nxTO315g7c|Bk-L zfaG<;pbU5l8X*cRv9L2JXw=|y1iD;xnjS1dKkxTfK@nT`F9Ew5D9F>4bh~(rnSG8{ z9IwEKi+b)Ufs3v5z@c9-(K+Snz+2!?KjqVA*sc&7?)`d7C_O6|h0Pt^l*|)UbjczO zpHqs6{c1tr{jM{Ly^B^h(GM`fHiqD-BOIzeI|&rCUIitq#~4@XO$B|~3_nj_UbE*a z_zsUE(wSO(Of&e;r{TG7?{D~uDlKGfEwz9w%aCkX=Os-r_7})Orh+paP4EeXiDW3t zT2Q`-u^N!^S6smWBcueERN)jwd!i#FYW(a`Q(s>b2 zBl1;=3cRN8@g?$5rZwm&>RT`nX;=;&DABsmLiTnzs1o-Ne$v fC9`oMo%a0)Lk#jiOZi|800000NkvXXu0mjfdhQk9 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/time.png b/code/nel/tools/3d/object_viewer_qt/src/images/time.png new file mode 100644 index 0000000000000000000000000000000000000000..09ef0c6c62dfd96cbda75e4dd48d33e3fe5aade1 GIT binary patch literal 2312 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 literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/water.png b/code/nel/tools/3d/object_viewer_qt/src/images/water.png new file mode 100644 index 0000000000000000000000000000000000000000..0b3be8795d24b0926ea4ed49e9bc64c51185c792 GIT binary patch literal 7199 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 O06s}6Nmh!(g8vIg_s8e} literal 0 HcmV?d00001 diff --git a/code/nel/tools/3d/object_viewer_qt/src/images/wind.png b/code/nel/tools/3d/object_viewer_qt/src/images/wind.png new file mode 100644 index 0000000000000000000000000000000000000000..f16d389316fee5e1fc001513420481209e5c1e56 GIT binary patch literal 10396 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 + + CLocatedBindablePage + + + + 0 + 0 + 352 + 494 + + + + + 0 + 0 + + + + Form + + + + + + + 0 + 0 + + + + QTabWidget::East + + + 0 + + + Qt::ElideNone + + + false + + + true + + + + + 0 + 0 + + + + + + + + + + + + Blending mode: + + + + + + + + Add + + + + + Modulate + + + + + Alpha blend + + + + + Alpha test + + + + + + + + + + Global color lighting + + + + + + + Z-Test + + + + + + + + + ZBias: + + + + + + + -9999.000000000000000 + + + 9999.000000000000000 + + + + + + + + + Qt::Vertical + + + + 20 + 21 + + + + + + + + No AutoLOD + + + + + + + Color + + + + + + + + + 0 + 0 + + + + + + + + + + Independant size + + + + + + + false + + + + + + + + + Width + + + true + + + + + + + Height + + + + + + + + + + Qt::Vertical + + + + 31 + 354 + + + + + + + + Size + + + + + + + Angle 2D + + + + + + + + + 0 + 0 + + + + + + + + + + hint / precomputed rotations + + + + + + + false + + + + + + + + + Rot speed min: + + + + + + + + + + Rot speed max: + + + + + + + + + + Nb models: + + + + + + + 1 + + + 32 + + + + + + + + + + Qt::Vertical + + + + 17 + 184 + + + + + + + + Rotation + + + + + + + + + 0 + 0 + + + + + + + + + + Align on motion + + + + + + + Z-Align + + + + + + + Fake motion blur coeff.: + + + + + + + + + + Fake motion blur treshold: + + + + + + + + + + Qt::Vertical + + + + 20 + 203 + + + + + + + + + + 0 + 0 + + + + + + + + + + Radius cut: + + + + + + + + + + Number of segs: + + + + + + + + + + Texture U factor: + + + + + + + + + + Qt::Vertical + + + + 20 + 251 + + + + + + + + + + 0 + 0 + + + + + + + + + + Number fan light: + + + + + + + + + + Phase smoothnes: + + + + + + + + + + Fan light speed: + + + + + + + + + + Fan light intensity: + + + + + + + + + + Qt::Vertical + + + + 20 + 229 + + + + + + + + + + 0 + 0 + + + + + + + + + + Nb segs: + + + + + + + + + + Seg duration: + + + + + + + + + + Texture U factor: + + + + + + + + + + Texture V factor: + + + + + + + + + + + + + + + + Use hermitte interpolation + + + + + + + Non parametric anim only + + + Force constant length + + + + + + + Trail coord. system: + + + + + + + + Local + + + + + World + + + + + User + + + + + Father coord. system + + + + + + + + Length(m): + + + + + + + false + + + + + + + LOD degradation: + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 133 + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 407 + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 463 + + + + + + + + + + + + + NLQT::CEditRangeUIntWidget + QWidget +

edit_range_widget.h
+ 1 + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CAttribFloatWidget + QGroupBox +
attrib_widget.h
+ 1 +
+ + NLQT::CParticleTextureWidget + QWidget +
particle_texture_widget.h
+ 1 +
+ + NLQT::CMeshWidget + QWidget +
mesh_widget.h
+ 1 +
+ + NLQT::CConstraintMeshWidget + QWidget +
constraint_mesh_widget.h
+ 1 +
+ + NLQT::CTailParticleWidget + QWidget +
tail_particle_widget.h
+ 1 +
+ + NLQT::CParticleTextureAnimWidget + QWidget +
particle_texture_anim_widget.h
+ 1 +
+ + NLQT::CAttribRGBAWidget + QGroupBox +
attrib_widget.h
+ 1 +
+ + NLQT::CAttribPlaneBasisWidget + QGroupBox +
attrib_widget.h
+ 1 +
+ + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.cpp new file mode 100644 index 000000000..b5c67c8a2 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.cpp @@ -0,0 +1,686 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "located_bindable_page.h" + +// Qt includes + +// NeL includes +#include +#include "nel/3d/ps_mesh.h" + +// Project includes +#include "modules.h" + +namespace NLQT { + +CLocatedBindablePage::CLocatedBindablePage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + // Particle color + _ui.colorWidget->setWrapper(&_ColorWrapper); + _ui.colorWidget->setSchemeWrapper(&_ColorWrapper); + _ui.colorWidget->init(); + + // Particle angle 2d + _ui.angle2DWidget->setRange(0.f, 256.f); + _ui.angle2DWidget->setWrapper(&_Angle2DWrapper); + _ui.angle2DWidget->setSchemeWrapper(&_Angle2DWrapper); + _ui.angle2DWidget->init(); + + // Particle size + _ui.sizeWidget->setRange(0.0, 1.0); + _ui.sizeWidget->setWrapper(&_SizeWrapper); + _ui.sizeWidget->setSchemeWrapper(&_SizeWrapper); + _ui.sizeWidget->init(); + + // Particle plane bias + _ui.particlePlaneBasicWidget->setWrapper(&_PlaneBasisWrapper); + _ui.particlePlaneBasicWidget->setSchemeWrapper(&_PlaneBasisWrapper); + _ui.particlePlaneBasicWidget->init(); + + // Fake motion blur coeff + _ui.blurCoeffWidget->setRange(0.0, 5.0); + _ui.blurCoeffWidget->setWrapper(&_MotionBlurCoeffWrapper); + + // Fake motion blur threshold + _ui.blurTresholdWidget->setRange(0.0, 5.0); + _ui.blurTresholdWidget->setWrapper(&_MotionBlurThresholdWrapper); + + // Shock wave radius cut + _ui.radiusCutWidget->setRange(0, 1); + _ui.radiusCutWidget->setWrapper(&_RadiusCutWrapper); + + // Shock wave number segment + _ui.shockWaveNbSegWidget->setRange(3, 24); + _ui.shockWaveNbSegWidget->enableLowerBound(3, false); + _ui.shockWaveNbSegWidget->setWrapper(&_ShockWaveNbSegWrapper); + + // Shock wave texture U factor + _ui.shockWaveUFactorWidget->setRange(0, 5); + _ui.shockWaveUFactorWidget->setWrapper(&_ShockWaveUFactorWrapper); + + // Num fan light + _ui.nbFanLightWidget->setRange(3, 127); + _ui.nbFanLightWidget->enableLowerBound(3, false); + _ui.nbFanLightWidget->enableUpperBound(128, true); + _ui.nbFanLightWidget->setWrapper(&_FanLightWrapper); + + // Fan light phase smoothnes + _ui.phaseSmoothnesWidget->setRange(0, 31); + _ui.phaseSmoothnesWidget->enableUpperBound(32, true); + _ui.phaseSmoothnesWidget->setWrapper(&_FanLightSmoothnessWrapper); + + // Fan light speed + _ui.fanLightSpeedWidget->setRange(0, 4.f); + _ui.fanLightSpeedWidget->setWrapper(&_FanLightPhaseWrapper); + + // Fan light intensity + _ui.fanLightIntensityWidget->setRange(0, 4.f); + _ui.fanLightIntensityWidget->setWrapper(&_FanLightIntensityWrapper); + + // Tail number segs / Look At ribbon tail nb segs + _ui.tailNbSegsWidget->setRange(2, 16); + _ui.tailNbSegsWidget->enableLowerBound(1, true); + _ui.tailNbSegsWidget->setWrapper(&_TailParticleWrapper); + + // Ribbon texture U factor + _ui.ribbonTexUfactorWidget->setRange(0, 5); + _ui.ribbonTexUfactorWidget->setWrapper(&_RibbonUFactorWrapper); + + // Ribbon texture V factor + _ui.ribbonTexVfactorWidget->setRange(0, 5); + _ui.ribbonTexVfactorWidget->setWrapper(&_RibbonVFactorWrapper); + + // Ribbon segment duration + _ui.segDurationWidget->setRange(0.05f, 0.5f); + _ui.segDurationWidget->enableLowerBound(0, true); + _ui.segDurationWidget->setWrapper(&_SegDurationWrapper); + + // Length + _ui.ribbonLengthWidget->setRange(0.1f, 10.1f); + _ui.ribbonLengthWidget->setWrapper(&_RibbonLengthWrapper); + _ui.ribbonLengthWidget->enableLowerBound(0.f, true); + + // Lod degradation + _ui.lodDegradationWidget->setRange(0.f, 1.f); + _ui.lodDegradationWidget->setWrapper(&_LODDegradationWrapper); + _ui.lodDegradationWidget->enableLowerBound(0.f, false); + _ui.lodDegradationWidget->enableUpperBound(1.f, false); + + _ui.particleTextureWidget->setWrapper(&_TextureNoAnimWrapper); + _ui.particleTextureWidget->enableRemoveButton(true); + + hideAllWidget(); + + connect(_ui.autoLodCheckBox, SIGNAL(toggled(bool)), this, SLOT(setAutoLOD(bool))); + connect(_ui.globalColorLightingCheckBox, SIGNAL(toggled(bool)), this, SLOT(setGlobalColorLight(bool))); + connect(_ui.independantSizeCheckBox, SIGNAL(toggled(bool)), this, SLOT(setIndependantSize(bool))); + connect(_ui.alignCheckBox, SIGNAL(toggled(bool)), this, SLOT(setAlignOnMotion(bool))); + connect(_ui.ztestCheckBox, SIGNAL(toggled(bool)), this, SLOT(setZTest(bool))); + connect(_ui.alignCheckBox, SIGNAL(toggled(bool)), this, SLOT(setZAlign(bool))); + connect(_ui.rotationPCCheckBox, SIGNAL(toggled(bool)), this, SLOT(setHint(bool))); + connect(_ui.blendModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setBlendMode(int))); + connect(_ui.zbiasDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setZBias(double))); + connect(_ui.widthRadioButton, SIGNAL(clicked()), this, SLOT(setSizeWidth())); + connect(_ui.heightRadioButton, SIGNAL(clicked()), this, SLOT(setSizeWidth())); + connect(_ui.rotSpeedMaxDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setRotSpeedMax(double))); + connect(_ui.rotSpeedMinDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setRotSpeedMin(double))); + connect(_ui.numModelsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setNumModels(int))); + connect(_ui.useHermitteCheckBox, SIGNAL(toggled(bool)), this, SLOT(setUseHermitteInterpolation(bool))); + connect(_ui.constantLengthCheckBox, SIGNAL(toggled(bool)), this, SLOT(setConstantLength(bool))); + connect(_ui.coordSystemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setTrailCoordSystem(int))); +} + +CLocatedBindablePage::~CLocatedBindablePage() +{ +} + +void CLocatedBindablePage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable) +{ + _Node = ownerNode; + _Bindable = locatedBindable; + + hideAllWidget(); + + // No Auto LOD + NL3D::CParticleSystem *ps = _Bindable->getOwner()->getOwner(); + if (ps->isSharingEnabled()) + { + _ui.autoLodCheckBox->show(); + if (ps->isAutoLODEnabled() == false) + _ui.autoLodCheckBox->setChecked(false); + else + _ui.autoLodCheckBox->setChecked(NLMISC::safe_cast(_Bindable)->isAutoLODDisabled()); + } + else + _ui.autoLodCheckBox->hide(); + + // has the particle a material ? + bool isMaterial = dynamic_cast(_Bindable) != NULL; + + _ui.blendModeComboBox->setVisible(isMaterial); + _ui.blendModeLabel->setVisible(isMaterial); + _ui.zbiasDoubleSpinBox->setVisible(isMaterial); + _ui.zbiasLabel->setVisible(isMaterial); + _ui.ztestCheckBox->setVisible(isMaterial); + + if (isMaterial) + { + NL3D::CPSMaterial *material = dynamic_cast(_Bindable); + // blending mode + _ui.blendModeComboBox->setCurrentIndex(material->getBlendingMode()); + // z-test + _ui.ztestCheckBox->setChecked(material->isZTestEnabled()); + // z-bias + _ui.zbiasDoubleSpinBox->setValue(-material->getZBias()); + } + + if (dynamic_cast(_Bindable)) + { + NL3D::CPSParticle *p = (NL3D::CPSParticle *) _Bindable; + + // check support for lighting + if (p->supportGlobalColorLighting()) + { + _ui.globalColorLightingCheckBox->show(); + // if global color lighting is forced for all objects, don't allow to modify + _ui.globalColorLightingCheckBox->setEnabled(!ps->getForceGlobalColorLightingFlag()); + _ui.globalColorLightingCheckBox->setChecked(p->usesGlobalColorLighting()); + } + else + _ui.globalColorLightingCheckBox->hide(); + + // check support for color + if (dynamic_cast(_Bindable)) + { + _ColorWrapper.S = dynamic_cast(_Bindable); + _ui.colorWidget->setWorkspaceNode(_Node); + _ui.colorWidget->updateUi(); + + // Add material page in tabWidget + _ui.tabWidget->addTab(_ui.materialPage, tr("Material")); + } + + if (dynamic_cast(_Bindable)) + { + updateSizeControl(); + + // Size/Angle2D page in tabWidget + _ui.tabWidget->addTab(_ui.sizeAnglePage, tr("Size/Angle 2D")); + } + + // check support for angle 2D + if (dynamic_cast(_Bindable)) + { + _Angle2DWrapper.S = dynamic_cast(_Bindable); + _ui.angle2DWidget->setWorkspaceNode(_Node); + _ui.angle2DWidget->updateUi(); + _ui.angle2DWidget->show(); + } + else + _ui.angle2DWidget->hide(); + + // check support for plane basis + if (dynamic_cast(_Bindable)) + { + _PlaneBasisWrapper.S = dynamic_cast(_Bindable); + _ui.particlePlaneBasicWidget->setWorkspaceNode(_Node); + _ui.particlePlaneBasicWidget->updateUi(); + _ui.particlePlaneBasicWidget->setEnabled(true); + + // Add material page in tabWidget + _ui.tabWidget->addTab(_ui.rotatePage, tr("Rotations")); + } + + // check support for precomputed rotations + bool isHintParticle = dynamic_cast(_Bindable) != NULL; + _ui.rotationPCCheckBox->setVisible(isHintParticle); + _ui.rotGroupBox->setVisible(isHintParticle); + if (isHintParticle) + { + NL3D::CPSHintParticleRotateTheSame *rotatedParticle = dynamic_cast(_Bindable); + float minValue, maxValue; + uint32 nbModels = rotatedParticle->checkHintRotateTheSame(minValue, maxValue); + _ui.rotationPCCheckBox->setChecked(nbModels != 0); + } + + // if we're dealing with a face look at, motion blur can be tuned + if (dynamic_cast(_Bindable)) + { + NL3D::CPSFaceLookAt *fla = static_cast(_Bindable); + _MotionBlurCoeffWrapper.P = fla; + _MotionBlurCoeffWrapper.OwnerNode = _Node; + _ui.blurCoeffWidget->updateUi(); + + _MotionBlurThresholdWrapper.P = fla; + _MotionBlurThresholdWrapper.OwnerNode = _Node; + _ui.blurTresholdWidget->updateUi(); + + _ui.zalignCheckBox->show(); + _ui.alignCheckBox->show(); + _ui.alignCheckBox->setChecked(fla->getAlignOnMotion()); + _ui.zalignCheckBox->setChecked(fla->getAlignOnZAxis()); + + // 'look at' independant sizes + _ui.independantSizeCheckBox->setChecked(fla->hasIndependantSizes()); + + _ui.independantSizeCheckBox->show(); + _ui.independantGroupBox->show(); + + // Add Look at page in tabWidget + _ui.tabWidget->addTab(_ui.lookAtPage, tr("Look At param")); + } + else + { + _ui.independantSizeCheckBox->hide(); + _ui.independantGroupBox->hide(); + } + + // Shock wave page setup + if (dynamic_cast(_Bindable)) + { + NL3D::CPSShockWave *sw = static_cast(_Bindable); + + _RadiusCutWrapper.OwnerNode = _Node; + _RadiusCutWrapper.S = sw; + _ui.radiusCutWidget->updateUi(); + + _ShockWaveNbSegWrapper.OwnerNode = _Node; + _ShockWaveNbSegWrapper.S = sw; + _ui.shockWaveNbSegWidget->updateUi(); + + _ShockWaveUFactorWrapper.OwnerNode = _Node; + _ShockWaveUFactorWrapper.S = sw; + _ui.shockWaveUFactorWidget->updateUi(); + + // Add Shock wave page in tabWidget + _ui.tabWidget->addTab(_ui.shockWavePage, tr("Shock wave param")); + } + + // Fan Light page setup + if (dynamic_cast(_Bindable)) + { + _FanLightWrapper.OwnerNode = _Node; + _FanLightWrapper.P = dynamic_cast(_Bindable); + _ui.nbFanLightWidget->updateUi(); + + _FanLightSmoothnessWrapper.OwnerNode = _Node; + _FanLightSmoothnessWrapper.P = static_cast(_Bindable); + _ui.phaseSmoothnesWidget->updateUi(); + + _FanLightPhaseWrapper.OwnerNode = _Node; + _FanLightPhaseWrapper.P = static_cast(_Bindable); + _ui.fanLightSpeedWidget->updateUi(); + + _FanLightIntensityWrapper.OwnerNode = _Node; + _FanLightIntensityWrapper.P = static_cast(_Bindable); + _ui.fanLightIntensityWidget->updateUi(); + + // Add Fan Light page in tabWidget + _ui.tabWidget->addTab(_ui.fanLightPage, tr("Fan Light param")); + } + + // tail particle + if (dynamic_cast(_Bindable)) + { + if (dynamic_cast(_Bindable)) + _ui.tailNbSegsWidget->enableUpperBound(256, true); + + _TailParticleWrapper.OwnerNode = _Node; + _TailParticleWrapper.P = dynamic_cast(_Bindable); + _ui.tailNbSegsWidget->updateUi(); + + _ui.tailWidget->setCurrentTailParticle(_Node, dynamic_cast(_Bindable)); + + _ui.ribbonTexUfactorWidget->hide(); + _ui.ribbonTexVfactorWidget->hide(); + _ui.tailTexUflabel->hide(); + _ui.tailTexVflabel->hide(); + + // Add tail page in tabWidget + _ui.tabWidget->addTab(_ui.ribbonTailPage, tr("Tail param")); + } + + // shape particle + if (dynamic_cast(_Bindable)) + { + _ui.meshWidget->setCurrentShape(_Node, dynamic_cast(_Bindable)); + + // Add mesh page in tabWidget + _ui.tabWidget->addTab(_ui.meshPage, tr("Mesh param")); + } + + // constraint mesh particle + if (dynamic_cast(_Bindable)) + { + _ui.constraintMeshWidget->setCurrentConstraintMesh(_Node, dynamic_cast(_Bindable)); + _ui.constraintMeshWidget->show(); + } + else + _ui.constraintMeshWidget->hide(); + + + // check support for animated texture + if (dynamic_cast(_Bindable)) + { + _ui.texAnimWidget->setCurrentTextureAnim(dynamic_cast(_Bindable), + dynamic_cast(_Bindable), + _Node); + _ui.texAnimWidget->show(); + + _ui.tabWidget->addTab(_ui.texturePage, tr("Texture param")); + } + else + _ui.texAnimWidget->hide(); + + // unanimated texture + if (dynamic_cast(_Bindable)) + { + _TextureNoAnimWrapper.TP = dynamic_cast(_Bindable); + _TextureNoAnimWrapper.OwnerNode = _Node; + + _ui.particleTextureWidget->updateUi(); + _ui.particleTextureWidget->show(); + + _ui.tabWidget->addTab(_ui.texturePage, tr("Texture param")); + } + else + _ui.particleTextureWidget->hide(); + + // ribbon texture (doesn't support texture animation for now) + if (dynamic_cast(_Bindable)) + { + _RibbonUFactorWrapper.OwnerNode = _Node; + _RibbonUFactorWrapper.R = static_cast(_Bindable); + _ui.ribbonTexUfactorWidget->updateUi(); + + _RibbonVFactorWrapper.OwnerNode = _Node; + _RibbonVFactorWrapper.R = static_cast(_Bindable); + _ui.ribbonTexVfactorWidget->updateUi(); + + _ui.ribbonTexUfactorWidget->show(); + _ui.ribbonTexVfactorWidget->show(); + _ui.tailTexUflabel->show(); + _ui.tailTexVflabel->show(); + + } + + if (dynamic_cast(_Bindable)) + { + NL3D::CPSRibbonBase *ribbon = static_cast(_Bindable); + + _SegDurationWrapper.R = ribbon; + _ui.segDurationWidget->updateUi(); + + // Length + _RibbonLengthWrapper.OwnerNode = _Node; + _RibbonLengthWrapper.R = ribbon; + _ui.ribbonLengthWidget->updateUi(); + + // Lod degradation + _LODDegradationWrapper.OwnerNode = _Node; + _LODDegradationWrapper.R = ribbon; + _ui.lodDegradationWidget->updateUi(); + + // Coord system + _ui.coordSystemComboBox->setCurrentIndex(ribbon->getMatrixMode()); + _ui.useHermitteCheckBox->setChecked(ribbon->getInterpolationMode() == NL3D::CPSRibbonBase::Hermitte); + _ui.constantLengthCheckBox->setChecked(ribbon->getRibbonMode() == NL3D::CPSRibbonBase::FixedSize); + } + } +} + +void CLocatedBindablePage::setAutoLOD(bool state) +{ + NL3D::CPSParticle *p = NLMISC::safe_cast(_Bindable); + nlassert(p); + if (state != p->isAutoLODDisabled()) + { + p->disableAutoLOD(state); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setBlendMode(int index) +{ + NL3D::CPSMaterial *m = dynamic_cast(_Bindable); + nlassert(m); + if (index != m->getBlendingMode()) + { + m->setBlendingMode( (NL3D::CPSMaterial::TBlendingMode)index); + touchPSState(); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setGlobalColorLight(bool state) +{ + NL3D::CPSParticle *p = NLMISC::safe_cast(_Bindable); + nlassert(p); + if (state != p->usesGlobalColorLighting()) + { + p->enableGlobalColorLighting(state); + touchPSState(); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setIndependantSize(bool state) +{ + NL3D::CPSFaceLookAt *la = static_cast(_Bindable); + nlassert(la); + if (state != la->hasIndependantSizes()) + { + la->setIndependantSizes(state); + updateModifiedFlag(); + } + _ui.independantGroupBox->setEnabled(state); + updateSizeControl(); +} + +void CLocatedBindablePage::setAlignOnMotion(bool state) +{ + NL3D::CPSFaceLookAt *fla = NLMISC::safe_cast(_Bindable); + nlassert(fla); + if (state != fla->getAlignOnMotion()) + { + fla->setAlignOnMotion(state); + updateModifiedFlag(); + } + updateValidWidgetForAlignOnMotion(!state); +} + +void CLocatedBindablePage::setZTest(bool state) +{ + NL3D::CPSMaterial *mat = dynamic_cast(_Bindable); + nlassert(mat); + if (state != mat->isZTestEnabled()) + { + mat->enableZTest(state); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setZAlign(bool state) +{ + NL3D::CPSFaceLookAt *fla = NLMISC::safe_cast(_Bindable); + nlassert(fla); + if (state != fla->getAlignOnZAxis()) + { + fla->setAlignOnZAxis(state); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setZBias(double value) +{ + NLMISC::safe_cast(_Bindable)->setZBias(-value); +} + +void CLocatedBindablePage::setSizeWidth() +{ + updateSizeControl(); +} + +void CLocatedBindablePage::setHint(bool state) +{ + NL3D::CPSHintParticleRotateTheSame *rotatedParticle = dynamic_cast(_Bindable); + float minVelocity, maxVelocity; + uint32 nbModels = rotatedParticle->checkHintRotateTheSame(minVelocity, maxVelocity); + nlassert(rotatedParticle); + if ((nbModels != 0) != state) + { + if (state) + _ui.numModelsSpinBox->setValue(32); + else + rotatedParticle->disableHintRotateTheSame(); + updateModifiedFlag(); + } + _ui.rotGroupBox->setEnabled(state); + _ui.particlePlaneBasicWidget->setEnabled(!state); + rotatedParticle->checkHintRotateTheSame(minVelocity, maxVelocity); + _ui.rotSpeedMaxDoubleSpinBox->setValue(maxVelocity); + _ui.rotSpeedMinDoubleSpinBox->setValue(minVelocity); +} + +void CLocatedBindablePage::setRotSpeedMax(double value) +{ + NL3D::CPSHintParticleRotateTheSame *rotatedParticle = dynamic_cast(_Bindable); + nlassert(rotatedParticle); + float valueMin, valueMax; + uint32 nbModels = rotatedParticle->checkHintRotateTheSame(valueMin, valueMax); + if (valueMax != value) + { + rotatedParticle->hintRotateTheSame(nbModels, valueMin, value); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setRotSpeedMin(double value) +{ + NL3D::CPSHintParticleRotateTheSame *rotatedParticle = dynamic_cast(_Bindable); + nlassert(rotatedParticle); + float valueMin, valueMax; + uint32 nbModels = rotatedParticle->checkHintRotateTheSame(valueMin, valueMax); + if (valueMin != value) + { + rotatedParticle->hintRotateTheSame(nbModels, value, valueMax); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setNumModels(int value) +{ + NL3D::CPSHintParticleRotateTheSame *rotatedParticle = dynamic_cast(_Bindable); + nlassert(rotatedParticle); + float valueMin, valueMax; + sint32 nbModels; + + nbModels = rotatedParticle->checkHintRotateTheSame(valueMin, valueMax); + if (nbModels != value) + { + rotatedParticle->hintRotateTheSame((uint32) value, valueMin, valueMax); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::setUseHermitteInterpolation(bool state) +{ + NL3D::CPSRibbonBase *ribbon = dynamic_cast(_Bindable); + ribbon->setInterpolationMode(state ? NL3D::CPSRibbonBase::Hermitte : NL3D::CPSRibbonBase::Linear); +} + +void CLocatedBindablePage::setConstantLength(bool state) +{ + NL3D::CPSRibbonBase *ribbon = dynamic_cast(_Bindable); + ribbon->setRibbonMode(state ? NL3D::CPSRibbonBase::FixedSize : NL3D::CPSRibbonBase::VariableSize); + _ui.ribbonLengthWidget->setEnabled(state); + _ui.ribbonLengthLabel->setEnabled(state); +} + +void CLocatedBindablePage::setTrailCoordSystem(int index) +{ + NL3D::CPSRibbonBase *ribbon = dynamic_cast(_Bindable); + if (index != ribbon->getMatrixMode()) + { + ribbon->setMatrixMode((NL3D::CPSRibbonBase::TMatrixMode) index); + updateModifiedFlag(); + } +} + +void CLocatedBindablePage::updateValidWidgetForAlignOnMotion(bool align) +{ + _ui.blurCoeffLabel->setEnabled(align); + _ui.blurCoeffWidget->setEnabled(align); + _ui.blurTreshholdLabel->setEnabled(align); + _ui.blurTresholdWidget->setEnabled(align); + _ui.zalignCheckBox->setEnabled(align); +} + +void CLocatedBindablePage::updateSizeControl() +{ + if (!dynamic_cast(_Bindable)) return; + + NL3D::CPSFaceLookAt *fla = dynamic_cast(_Bindable); + // LookAt case + if (fla && fla->hasIndependantSizes()) + { + if (_ui.widthRadioButton->isChecked()) + _ui.sizeWidget->setTitle(tr("Width")); + else + _ui.sizeWidget->setTitle(tr("Height")); + + if (_ui.widthRadioButton->isChecked()) // wrap to the wanted size + _SizeWrapper.S = fla; + else + _SizeWrapper.S = &fla->getSecondSize(); + } + else // general case. Wrap to the size interface and the appropriate dialog + { + _SizeWrapper.S = dynamic_cast(_Bindable); + _ui.sizeWidget->setTitle(tr("Size")); + } + + _ui.sizeWidget->setWorkspaceNode(_Node); + _ui.sizeWidget->updateUi(); +} + +void CLocatedBindablePage::hideAllWidget() +{ + for (int i = 0; i < _ui.tabWidget->count(); i++) + _ui.tabWidget->widget(i)->hide(); + + while (_ui.tabWidget->count() != 0) + _ui.tabWidget->removeTab(_ui.tabWidget->count() - 1); +} + +void CLocatedBindablePage::touchPSState() +{ + if (_Node && _Node->getPSModel()) + { + _Node->getPSModel()->touchTransparencyState(); + _Node->getPSModel()->touchLightableState(); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.h b/code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.h new file mode 100644 index 000000000..cde2bc254 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/located_bindable_page.h @@ -0,0 +1,260 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef LOCATED_BINDABLE_PAGE_H +#define LOCATED_BINDABLE_PAGE_H + +#include +#include "ui_located_bindable_form.h" + +// STL includes + +// NeL includes +#include "nel/misc/rgba.h" +#include "nel/3d/texture.h" +#include "nel/3d/ps_plane_basis.h" +#include "nel/3d/particle_system.h" +#include "nel/3d/ps_particle.h" +#include "nel/3d/ps_particle2.h" + +// Project includes +#include "ps_wrapper.h" +#include "particle_node.h" + +namespace NLQT { + +/** +@class CLocatedBindablePage +@brief Page for QStackWidget, to edit located bindables in a particle system +*/ +class CLocatedBindablePage: public QWidget +{ + Q_OBJECT + +public: + CLocatedBindablePage(QWidget *parent = 0); + ~CLocatedBindablePage(); + + /// Set the located bindable to edit. + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable); + +private Q_SLOTS: + void setAutoLOD(bool state); + void setBlendMode(int index); + void setGlobalColorLight(bool state); + void setIndependantSize(bool state); + void setAlignOnMotion(bool state); + void setZTest(bool state); + void setZAlign(bool state); + void setZBias(double value); + void setSizeWidth(); + void setHint(bool state); + void setRotSpeedMax(double value); + void setRotSpeedMin(double value); + void setNumModels(int value); + void setUseHermitteInterpolation(bool state); + void setConstantLength(bool state); + void setTrailCoordSystem(int index); + +private: + + /// Wrappers to various element of bindables + + /// Size + struct CSizeWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSSizedParticle *S; + float get(void) const { return S->getSize(); } + void set(const float &v) { S->setSize(v); } + scheme_type *getScheme(void) const { return S->getSizeScheme(); } + void setScheme(scheme_type *s) { S->setSizeScheme(s); } + } _SizeWrapper; + + /// Color + struct CColorWrapper : public IPSWrapperRGBA, IPSSchemeWrapperRGBA + { + NL3D::CPSColoredParticle *S; + NLMISC::CRGBA get(void) const { return S->getColor(); } + void set(const NLMISC::CRGBA &v) { S->setColor(v); } + scheme_type *getScheme(void) const { return S->getColorScheme(); } + void setScheme(scheme_type *s) { S->setColorScheme(s); } + } _ColorWrapper; + + /// Angle 2D + struct CAngle2DWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSRotated2DParticle *S; + float get(void) const { return S->getAngle2D(); } + void set(const float &v) { S->setAngle2D(v); } + scheme_type *getScheme(void) const { return S->getAngle2DScheme(); } + void setScheme(scheme_type *s) { S->setAngle2DScheme(s); } + } _Angle2DWrapper; + + /// Plane basis + struct CPlaneBasisWrapper : public IPSWrapper, IPSSchemeWrapper + { + NL3D::CPSRotated3DPlaneParticle *S; + NL3D::CPlaneBasis get(void) const { return S->getPlaneBasis(); } + void set(const NL3D::CPlaneBasis &p) { S->setPlaneBasis(p); } + scheme_type *getScheme(void) const { return S->getPlaneBasisScheme(); } + void setScheme(scheme_type *s) { S->setPlaneBasisScheme(s); } + } _PlaneBasisWrapper; + + /// Motion blur coeff + struct CMotionBlurCoeffWrapper : public IPSWrapperFloat + { + NL3D::CPSFaceLookAt *P; + float get(void) const { return P->getMotionBlurCoeff(); } + void set(const float &v) { P->setMotionBlurCoeff(v); } + } _MotionBlurCoeffWrapper; + struct CMotionBlurThresholdWrapper : public IPSWrapperFloat + { + NL3D::CPSFaceLookAt *P; + float get(void) const { return P->getMotionBlurThreshold(); } + void set(const float &v) { P->setMotionBlurThreshold(v); } + } _MotionBlurThresholdWrapper; + + /// Fanlight + struct CFanLightWrapper : public IPSWrapperUInt + { + NL3D::CPSFanLight *P; + uint32 get(void) const { return P->getNbFans(); } + void set(const uint32 &v) { P->setNbFans(v); } + } _FanLightWrapper; + + struct CFanLightSmoothnessWrapper : public IPSWrapperUInt + { + NL3D::CPSFanLight *P; + uint32 get(void) const { return P->getPhaseSmoothness(); } + void set(const uint32 &v) { P->setPhaseSmoothness(v); } + } _FanLightSmoothnessWrapper; + + struct CFanLightPhase : public IPSWrapperFloat + { + NL3D::CPSFanLight *P; + float get(void) const { return P->getPhaseSpeed(); } + void set(const float &v) { P->setPhaseSpeed(v); } + } _FanLightPhaseWrapper; + + struct CFanLightIntensityWrapper : public IPSWrapperFloat + { + NL3D::CPSFanLight *P; + float get(void) const { return P->getMoveIntensity(); } + void set(const float &v) { P->setMoveIntensity(v); } + } _FanLightIntensityWrapper; + + /// Ribbon / tail dot + struct CTailParticleWrapper : public IPSWrapperUInt + { + NL3D::CPSTailParticle *P; + uint32 get(void) const { return P->getTailNbSeg(); } + void set(const uint32 &v) { P->setTailNbSeg(v); } + } _TailParticleWrapper; + + /// Duration of segment for a ribbon + struct CSegDurationWrapper : public IPSWrapperFloat + { + NL3D::CPSRibbonBase *R; + float get(void) const { return R->getSegDuration(); } + void set(const float &v) { R->setSegDuration(v); } + } _SegDurationWrapper; + + /// Shockwave + struct CRadiusCutWrapper : public IPSWrapperFloat + { + NL3D::CPSShockWave *S; + float get(void) const { return S->getRadiusCut(); } + void set(const float &v) { S->setRadiusCut(v); } + } _RadiusCutWrapper; + + struct CShockWaveNbSegWrapper : public IPSWrapperUInt + { + NL3D::CPSShockWave *S; + uint32 get(void) const { return S->getNbSegs(); } + void set(const uint32 &v) { S->setNbSegs(v); } + } _ShockWaveNbSegWrapper; + + struct CShockWaveUFactorWrapper : public IPSWrapperFloat + { + NL3D::CPSShockWave *S; + float get(void) const { return S->getUFactor(); } + void set(const float &v) { S->setUFactor(v); } + } _ShockWaveUFactorWrapper; + + /// Unanimated texture + struct CTextureNoAnimWrapper : public IPSWrapperTexture + { + NL3D::CPSTexturedParticleNoAnim *TP; + virtual NL3D::ITexture *get(void) { return TP->getTexture(); } + virtual void set(NL3D::ITexture *t) { TP->setTexture(t); } + } _TextureNoAnimWrapper; + + /// u / v factors for ribbon + struct CRibbonUFactorWrapper : public IPSWrapperFloat + { + NL3D::CPSRibbon *R; + float get(void) const { return R->getUFactor(); } + void set(const float &u) { R->setTexFactor(u, R->getVFactor()); } + } _RibbonUFactorWrapper; + + struct CRibbonVFactorWrapper : public IPSWrapperFloat + { + NL3D::CPSRibbon *R; + float get(void) const { return R->getVFactor(); } + void set(const float &v) { R->setTexFactor(R->getUFactor(), v); } + } _RibbonVFactorWrapper; + + struct CRibbonLengthWrapper : IPSWrapperFloat + { + NL3D::CPSRibbonBase *R; + float get() const { return R->getRibbonLength(); } + void set(const float &v) { R->setRibbonLength(v); } + } _RibbonLengthWrapper; + + + struct CLODDegradationWrapper : IPSWrapperFloat + { + NL3D::CPSRibbonBase *R; + float get() const { return R->getLODDegradation(); } + void set(const float &v) { R->setLODDegradation(v); } + } _LODDegradationWrapper; + + void updateValidWidgetForAlignOnMotion(bool align); + + void updateSizeControl(); + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + void hideAllWidget(); + + void touchPSState(); + + CWorkspaceNode *_Node; + + /// The bindable being edited + NL3D::CPSLocatedBindable *_Bindable; + + Ui::CLocatedBindablePage _ui; + +}; /* class CLocatedBindablePage */ + +} /* namespace NLQT */ + + +#endif // LOCATED_BINDABLE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/located_form.ui b/code/nel/tools/3d/object_viewer_qt/src/located_form.ui new file mode 100644 index 000000000..106b1bebd --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/located_form.ui @@ -0,0 +1,233 @@ + + + CLocatedPage + + + + 0 + 0 + 291 + 410 + + + + Form + + + + + + QTabWidget::East + + + 0 + + + + General + + + + + + Coord system: + + + + + + + + Particles are local to the system + + + + + Particles are in world + + + + + User + + + + + + + + Disgrade with lod + + + + + + + Parametric motion + + + + + + + Max number of particles: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Set current count particles + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 61 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 224 + + + + + + + + + Life time + + + + + + Limited life time + + + + + + + + + + + + + Triger on death + + + + + + + Edit + + + + + + + + + + Qt::Vertical + + + + 20 + 251 + + + + + + + + + + + + Particle mass + + + + + + Qt::Vertical + + + + 20 + 274 + + + + + + + + + + + + + + + + NLQT::CEditRangeUIntWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CAttribFloatWidget + QGroupBox +
attrib_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/located_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/located_page.cpp new file mode 100644 index 000000000..f63d2ead4 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/located_page.cpp @@ -0,0 +1,264 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "located_page.h" + +// Qt includes +#include +#include + +// NeL includes +#include + +// Project includes +#include "modules.h" + +namespace NLQT { + +CLocatedPage::CLocatedPage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.lifeWidget->setRange(0, 10); + _ui.lifeWidget->setWrapper(&_LifeWrapper); + _ui.lifeWidget->setSchemeWrapper(&_LifeWrapper); + /// WARNING:// 0 is disallowed + ///_ui.lifeWidget->enableLowerBound(0, true); + _ui.lifeWidget->enableMemoryScheme(false); + _ui.lifeWidget->init(); + + _ui.massWidget->setRange(0.001f, 10); + _ui.massWidget->setWrapper(&_MassWrapper); + _ui.massWidget->setSchemeWrapper(&_MassWrapper); + /// WARNING:// 0 is disallowed + ///_ui.massWidget->enableLowerBound(0, true); + _ui.massWidget->enableMemoryScheme(false); + _ui.massWidget->init(); + + _ui.maxNumParticleWidget->setRange(1, 501); + _ui.maxNumParticleWidget->setWrapper(&_MaxNbParticlesWrapper); + + connect(_ui.coordSystemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setMatrixMode(int))); + connect(_ui.disgradeWithLODCheckBox, SIGNAL(toggled(bool)), this, SLOT(setDisgradeWithLod(bool))); + connect(_ui.parametricMotionCheckBox, SIGNAL(toggled(bool)), this, SLOT(setParametricMotion(bool))); + connect(_ui.trigerOnDeathCheckBox, SIGNAL(toggled(bool)), this, SLOT(setTriggerOnDeath(bool))); + connect(_ui.editPushButton, SIGNAL(clicked()), this, SLOT(editTriggerOnDeath())); + connect(_ui.limitedLifeTimeCheckBox, SIGNAL(toggled(bool)), this, SLOT(setLimitedLifeTime(bool))); + connect(_ui.setCurrentCountPushButton, SIGNAL(clicked()), this, SLOT(setCount())); +} + +CLocatedPage::~CLocatedPage() +{ +} + +void CLocatedPage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocated *located) +{ + _Located = located; + _Node = ownerNode; + + _LifeWrapper.Located = _Located; + _LifeWrapper.Node = _Node; + _ui.lifeWidget->setWorkspaceNode(_Node); + _ui.lifeWidget->updateUi(); + + if (_Located->getLastForever()) + _ui.lifeWidget->setEnabled(false); + + + _MassWrapper.Located = _Located; + _ui.massWidget->setWorkspaceNode(_Node); + _ui.massWidget->updateUi(); + + if (_Located->getOwner()) + _ui.maxNumParticleWidget->setEnabled(!_Located->getOwner()->getAutoCountFlag()); + + _MaxNbParticlesWrapper.Located = _Located; + _MaxNbParticlesWrapper.Node = _Node; + _ui.maxNumParticleWidget->updateUi(); + + _ui.coordSystemComboBox->setCurrentIndex(int(_Located->getMatrixMode())); + _ui.limitedLifeTimeCheckBox->setChecked(!_Located->getLastForever()); + _ui.lifeWidget->setEnabled(!_Located->getLastForever()); + _ui.trigerOnDeathCheckBox->setChecked(_Located->isTriggerOnDeathEnabled()); + updateTriggerOnDeath(); + + _ui.disgradeWithLODCheckBox->setChecked(_Located->hasLODDegradation()); + updateIntegrable(); + updateTriggerOnDeath(); +} + +void CLocatedPage::setDisabledCountPS(bool state) +{ + _ui.setCurrentCountPushButton->setEnabled(!state); + _ui.maxNumParticleWidget->setEnabled(!state); +} + +void CLocatedPage::setLimitedLifeTime(bool state) +{ + if (state != _Located->getLastForever()) return; + if (!state) + { + bool forceApplied = false; + // check that no force are applied on the located + std::vector targeters; + _Located->getOwner()->getTargeters(_Located, targeters); + for(uint k = 0; k < targeters.size(); ++k) + { + if (targeters[k]->getType() == NL3D::PSForce) + { + forceApplied = true; + break; + } + } + if (forceApplied) + { + int ret = QMessageBox::critical(this, tr("NeL particle system editor"), + tr("The object has force(s) applied on it. If it last forever, " + "its motion can become instable after a while. Continue anyway ? (clue : you've been warned ..)"), + QMessageBox::Ok | QMessageBox::Cancel); + + if (ret == QMessageBox::Cancel) + { + _ui.limitedLifeTimeCheckBox->setChecked(true); + return; + } + } + if (_Located->setLastForever()) + { + _ui.lifeWidget->setEnabled(false); + } + else + { + QMessageBox::critical(this, tr("NeL particle system editor"), + tr("Can't perform operation : the system is flagged with 'No max nb steps' or uses the preset 'Spell FX', " + "and thus, should have a finite duration. Please remove that flag first."), + QMessageBox::Ok); + _ui.limitedLifeTimeCheckBox->setChecked(true); + } + } + else + { + _Located->setInitialLife(_Located->getInitialLife()); + _ui.lifeWidget->setEnabled(true); + } + updateTriggerOnDeath(); + Modules::psEdit().resetAutoCount(_Node); + updateModifiedFlag(); +} + +void CLocatedPage::setDisgradeWithLod(bool state) +{ + if (state != _Located->hasLODDegradation()) + { + _Located->forceLODDegradation(state); + updateModifiedFlag(); + } +} + +void CLocatedPage::setParametricMotion(bool state) +{ + if (state != _Located->isParametricMotionEnabled()) + { + _Located->enableParametricMotion(state); + updateModifiedFlag(); + } +} + +void CLocatedPage::editTriggerOnDeath() +{ + bool ok; + int i = QInputDialog::getInt(this, tr("Set the extern ID"), + tr("0 means no extern access."), + _Located->getTriggerEmitterID(), 0, 9999, 1, &ok); + if (ok) + { + _Located->setTriggerEmitterID(uint32(i)); + updateModifiedFlag(); + } +} + +void CLocatedPage::setTriggerOnDeath(bool state) +{ + if (state != _Located->isTriggerOnDeathEnabled()) + { + _Located->enableTriggerOnDeath(state); + updateTriggerOnDeath(); + updateModifiedFlag(); + } +} + +void CLocatedPage::setMatrixMode(int index) +{ + nlassert(_Located); + if (index != _Located->getMatrixMode()) + { + _Located->setMatrixMode((NL3D::TPSMatrixMode) index); + updateIntegrable(); + updateModifiedFlag(); + } +} + +void CLocatedPage::setCount() +{ + _Located->resize(_Located->getSize()); // set new max size + _ui.maxNumParticleWidget->updateUi(); + updateModifiedFlag(); +} + +void CLocatedPage::updateIntegrable(void) +{ + _ui.parametricMotionCheckBox->setChecked(_Located->isParametricMotionEnabled()); + _ui.parametricMotionCheckBox->setEnabled(_Located->supportParametricMotion()); +} + +void CLocatedPage::updateTriggerOnDeath(void) +{ + nlassert(_Located); + bool enable = !_Located->getLastForever(); + _ui.trigerOnDeathCheckBox->setEnabled(enable); + _ui.editPushButton->setEnabled(enable && _Located->isTriggerOnDeathEnabled()); +} + +void CLocatedPage::CMaxNbParticlesWrapper::set(const uint32 &v) +{ + // if the max new size is lower than the current number of instance, we must suppress item + // in the the CParticleTreeCtrl + if (v < Located->getSize()) + { + nlassert(Node); + /// WARNING: + ///TreeCtrl->suppressLocatedInstanceNbItem(*Node, v); + } + Located->resize(v); +} + +void CLocatedPage::CLifeWrapper::set(const float &v) +{ + Located->setInitialLife(v); + Modules::psEdit().resetAutoCount(Node); +} +void CLocatedPage::CLifeWrapper::setScheme(scheme_type *s) +{ + Located->setLifeScheme(s); + Modules::psEdit().resetAutoCount(Node); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/located_page.h b/code/nel/tools/3d/object_viewer_qt/src/located_page.h new file mode 100644 index 000000000..ba5100114 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/located_page.h @@ -0,0 +1,117 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef LOCATED_PAGE_H +#define LOCATED_PAGE_H + +#include +#include "ui_located_form.h" + +// Qt includes + +// STL includes + +// NeL includes + +// Project includes + +namespace NL3D +{ + class CPSLocated; +} + +namespace NLQT { + +/** +@class CLocatedPage +@brief Page for QStackWidget, to edit located in a particle system +*/ +class CLocatedPage: public QWidget +{ + Q_OBJECT + +public: + CLocatedPage(QWidget *parent = 0); + ~CLocatedPage(); + + /// Set the located to edit. + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocated *located); + +private Q_SLOTS: + void setDisabledCountPS(bool state); + void setLimitedLifeTime(bool state); + void setDisgradeWithLod(bool state); + void setParametricMotion(bool state); + void editTriggerOnDeath(); + void setTriggerOnDeath(bool state); + void setMatrixMode(int index); + void setCount(); + +private: + + /// wrapper to tune the max number of particles + struct CMaxNbParticlesWrapper : public IPSWrapperUInt + { + CWorkspaceNode *Node; + NL3D::CPSLocated *Located; + uint32 get(void) const { return Located->getMaxSize(); } + void set(const uint32 &v); + } _MaxNbParticlesWrapper; + + + /// wrapper to tune the mass of particles + struct CMassWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSLocated *Located; + float get(void) const { return Located->getInitialMass(); } + void set(const float &v) { Located->setInitialMass(v); } + virtual scheme_type *getScheme(void) const { return Located->getMassScheme(); } + virtual void setScheme(scheme_type *s) { Located->setMassScheme(s); } + } _MassWrapper; + + struct CLifeWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSLocated *Located; + CWorkspaceNode *Node; + float get(void) const { return Located->getInitialLife(); } + void set(const float &v); + virtual scheme_type *getScheme(void) const { return Located->getLifeScheme(); } + virtual void setScheme(scheme_type *s); + } _LifeWrapper; + + /// the located this dialog is editing + NL3D::CPSLocated *_Located; + + CWorkspaceNode *_Node; + + /// update the integrable check box + void updateIntegrable(void); + + /// update the 'trigger on death' control + void updateTriggerOnDeath(void); + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + Ui::CLocatedPage _ui; +}; /* class CLocatedPage */ + +} /* namespace NLQT */ + + +#endif // LOCATED_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/main.cpp b/code/nel/tools/3d/object_viewer_qt/src/main.cpp new file mode 100644 index 000000000..3e11d0648 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/main.cpp @@ -0,0 +1,101 @@ +#include "stdpch.h" +#include +#include + +// Qt includes +#include +#include + +// NeL includes +#include +#include +#include +#include +#include + +// Project includes +#include "modules.h" + +// nel_qt log file name +#define NLQT_LOG_FILE "nel_qt.log" + +// clear nel_qt log before use +#define NLQT_ERASE_LOG true + +#if !defined (NLQT_USE_LOG_LOG) +# define NLQT_USE_LOG_LOG true +#endif +#if !defined (NLQT_USE_LOG) +# define NLQT_USE_LOG 1 +#endif + +using namespace std; +using namespace NLMISC; + +namespace NLQT { + +namespace { + +CFileDisplayer *s_FileDisplayer = NULL; + +} /* anonymous namespace */ + +} /* namespace NLQT */ + +#ifndef DATA_DIR +# define DATA_DIR "." +#endif + + +#ifdef NL_OS_WINDOWS +# ifdef _UNICODE +# define tstring wstring +# else +# define tstring string +# endif +#endif + +sint main(int argc, char **argv) +{ + // go nel! + { + // use log.log if NEL_LOG_IN_FILE and NLQT_USE_LOG_LOG defined as 1 + createDebug(NULL, NLQT_USE_LOG_LOG, false); +#if NLQT_USE_LOG + // create toverhex_client.log + // filedisplayer only deletes the 001 etc + if (NLQT_ERASE_LOG && CFile::isExists(NLQT_LOG_FILE)) + CFile::deleteFile(NLQT_LOG_FILE); + // initialize the log file + NLQT::s_FileDisplayer = new CFileDisplayer(); + NLQT::s_FileDisplayer->setParam(NLQT_LOG_FILE, NLQT_ERASE_LOG); + DebugLog->addDisplayer(NLQT::s_FileDisplayer); + InfoLog->addDisplayer(NLQT::s_FileDisplayer); + WarningLog->addDisplayer(NLQT::s_FileDisplayer); + AssertLog->addDisplayer(NLQT::s_FileDisplayer); + ErrorLog->addDisplayer(NLQT::s_FileDisplayer); +#endif + + nlinfo("Welcome to NeL!"); + + NLMISC::CPath::remapExtension("tga", "png", true); + } + + QApplication app(argc, argv); + QSplashScreen *splash = new QSplashScreen(); + splash->setPixmap(QPixmap(":/images/nel_ide_load.png")); + splash->show(); + + Modules::init(); + + // load and set remap extensions from config + Modules::config().configRemapExtensions(); + // load and set search paths from config + Modules::config().configSearchPaths(); + + Modules::mainWin().showMaximized(); + splash->finish(&Modules::mainWin()); + int result = app.exec(); + Modules::release(); + return result; +} diff --git a/code/nel/tools/3d/object_viewer_qt/src/main_window.cpp b/code/nel/tools/3d/object_viewer_qt/src/main_window.cpp new file mode 100644 index 000000000..85fd0c099 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/main_window.cpp @@ -0,0 +1,636 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "main_window.h" + +// STL includes + +// Qt includes +#include + +// NeL includes +#include + +// Project includes +#include "modules.h" +#include "settings_dialog.h" +#include "graphics_viewport.h" +#include "animation_dialog.h" +#include "animation_set_dialog.h" +#include "particle_control_dialog.h" +#include "particle_workspace_dialog.h" +#include "slot_manager_dialog.h" +#include "setup_fog_dialog.h" +#include "skeleton_scale_dialog.h" +#include "skeleton_tree_model.h" +#include "water_pool_dialog.h" +#include "vegetable_dialog.h" +#include "global_wind_dialog.h" +#include "day_night_dialog.h" +#include "sun_color_dialog.h" + +using namespace std; +using namespace NLMISC; + +namespace NLQT { + +CMainWindow::CMainWindow(QWidget *parent) + : QMainWindow(parent), + _isGraphicsInitialized(false), _isGraphicsEnabled(false), + _isSoundInitialized(false), _isSoundEnabled(false), + _isLandscapeInitialized(false), _isLandscapeEnabled(false), + _GraphicsViewport(NULL), _lastDir(".") +{ + nldebug("CMainWindow::CMainWindow:"); + + // create NeL viewport + _GraphicsViewport = new CGraphicsViewport(this); + setCentralWidget(_GraphicsViewport); + + setDockNestingEnabled(true); + + // setup Qt style and palette from config file + _originalPalette = QApplication::palette(); + Modules::config().setAndCallback("QtStyle", CConfigCallback(this, &CMainWindow::cfcbQtStyle)); + Modules::config().setAndCallback("QtPalette", CConfigCallback(this, &CMainWindow::cfcbQtPalette)); + + _SkeletonTreeModel = new CSkeletonTreeModel(this); + + createDialogs(); + createActions(); + createMenus(); + createToolBars(); + createStatusBar(); + + setWindowIcon(QIcon(":/images/nel.png")); + + QSettings settings("object_viewer_qt.ini", QSettings::IniFormat); + settings.beginGroup("WindowSettings"); + restoreState(settings.value("QtWindowState").toByteArray()); + restoreGeometry(settings.value("QtWindowGeometry").toByteArray()); + settings.endGroup(); + + _isGraphicsEnabled = true; + _isLandscapeEnabled = true; + + // As a special case, a QTimer with a timeout of 0 will time out as soon as all the events in the window system's event queue have been processed. + // This can be used to do heavy work while providing a snappy user interface. + _mainTimer = new QTimer(this); + connect(_mainTimer, SIGNAL(timeout()), this, SLOT(updateRender())); + // timer->start(); // <- timeout 0 + // it's heavy on cpu, though, when no 3d driver initialized :) + _mainTimer->start(25); // 25fps + + _statusBarTimer = new QTimer(this); + connect(_statusBarTimer, SIGNAL(timeout()), this, SLOT(updateStatusBar())); + _statusBarTimer->start(5000); + + Modules::config().setAndCallback("SoundEnabled", CConfigCallback(this, &CMainWindow::cfcbSoundEnabled)); +} + +CMainWindow::~CMainWindow() +{ + nldebug("CMainWindow::~CMainWindow:"); + + // save state & geometry of window and widgets + QSettings settings("object_viewer_qt.ini", QSettings::IniFormat); + settings.beginGroup("WindowSettings"); + settings.setValue("QtWindowState", saveState()); + settings.setValue("QtWindowGeometry", saveGeometry()); + settings.endGroup(); + + Modules::config().dropCallback("SoundEnabled"); + Modules::config().dropCallback("QtPalette"); + Modules::config().dropCallback("QtStyle"); + + _mainTimer->stop(); + _statusBarTimer->stop(); + + delete _AnimationDialog; + delete _AnimationSetDialog; + delete _SlotManagerDialog; + delete _SetupFog; + delete _ParticleControlDialog; + delete _ParticleWorkspaceDialog; + delete _GraphicsViewport; +} + +void CMainWindow::setVisible(bool visible) +{ + // called by show() + // code assuming visible window needed to init the 3d driver + if (visible != isVisible()) + { + if (visible) + { + QMainWindow::setVisible(true); + updateInitialization(true); + } + else + { + updateInitialization(false); + QMainWindow::setVisible(false); + } + } +} + +int CMainWindow::getFrameRate() +{ + return _AnimationDialog->_frameRate; +} + +void CMainWindow::open() +{ + QStringList fileNames = QFileDialog::getOpenFileNames(this, + tr("Open NeL data file"), _lastDir, + tr("All NeL files (*.shape *.ps);;" + "NeL shape files (*.shape);;" + "NeL particle system files (*.ps)")); + + setCursor(Qt::WaitCursor); + if (!fileNames.isEmpty()) + { + QStringList list = fileNames; + _lastDir = QFileInfo(list.front()).absolutePath(); + + QString skelFileName = QFileDialog::getOpenFileName(this, + tr("Open skeleton file"), _lastDir, + tr("NeL skeleton file (*.skel)")); + + Q_FOREACH(QString fileName, list) + loadFile(fileName, skelFileName); + + _AnimationSetDialog->updateListObject(); + _AnimationSetDialog->updateListAnim(); + _SlotManagerDialog->updateUiSlots(); + } + setCursor(Qt::ArrowCursor); +} + +void CMainWindow::resetScene() +{ + Modules::objView().resetScene(); + _AnimationSetDialog->updateListObject(); + _AnimationSetDialog->updateListAnim(); + _SlotManagerDialog->updateUiSlots(); + _SkeletonTreeModel->resetTreeModel(); +} + +void CMainWindow::settings() +{ + CSettingsDialog _settingsDialog(this); + _settingsDialog.show(); + _settingsDialog.exec(); +} + +void CMainWindow::about() +{ + QMessageBox::about(this, tr("About Object Viewer Qt"), + tr("

Object Viewer Qt 8-)

" + "

Authors: dnk-88, sfb, Kaetemi, kervala

Compiled on %1 %2").arg(__DATE__).arg(__TIME__)); +} + +void CMainWindow::updateStatusBar() +{ + if (_isGraphicsInitialized) + statusBar()->showMessage(QString(Modules::objView().getDriver()->getVideocardInformation())); +} + +void CMainWindow::updateInitialization(bool visible) +{ + bool done; + do + { + done = true; // set false whenever change + bool wantSound = _isSoundEnabled && visible; + bool wantGraphics = _isGraphicsEnabled && visible; + // TODO WARNING: made Landscape stuff + bool wantLandscape = wantGraphics && _isGraphicsInitialized && _isLandscapeEnabled; + + // .. stuff that depends on other stuff goes on top to prioritize deinitialization + + // Landscape + if (_isLandscapeInitialized) + { + if (!wantLandscape) + { + _isLandscapeInitialized = false; +// if (_isGraphicsInitialized) +// Modules::veget().releaseGraphics(); + Modules::veget().release(); + done = false; + } + } + else + { + if (wantLandscape) + { + Modules::veget().init(); +// if (_isGraphicsInitialized) +// Modules::veget().initGraphics(); + _isLandscapeInitialized = true; + done = false; + } + } + + // Graphics (Driver) + if (_isGraphicsInitialized) + { + if (!wantGraphics) + { + _isGraphicsInitialized = false; + if (_isSoundInitialized) + Modules::sound().releaseGraphics(); + _GraphicsViewport->release(); + done = false; + } + } + else + { + if (wantGraphics) + { + _GraphicsViewport->init(); + if (_isSoundInitialized) + Modules::sound().initGraphics(); + _isGraphicsInitialized = true; + done = false; + } + } + + // Sound (AudioMixer) + if (_isSoundInitialized) + { + if (!wantSound) + { + _isSoundInitialized = false; + if (_isGraphicsInitialized) + Modules::sound().releaseGraphics(); + Modules::sound().release(); + done = false; + } + } + else + { + if (wantSound) + { + Modules::sound().init(); + if (_isGraphicsInitialized) + Modules::sound().initGraphics(); + _isSoundInitialized = true; + done = false; + } + } + + } while (!done); +} + +void CMainWindow::createActions() +{ + _openAction = new QAction(tr("&Open..."), this); + _openAction->setIcon(QIcon(":/images/open-file.png")); + _openAction->setShortcut(QKeySequence::Open); + _openAction->setStatusTip(tr("Open an existing file")); + connect(_openAction, SIGNAL(triggered()), this, SLOT(open())); + + _exitAction = new QAction(tr("E&xit"), this); + _exitAction->setShortcut(tr("Ctrl+Q")); + _exitAction->setStatusTip(tr("Exit the application")); + connect(_exitAction, SIGNAL(triggered()), this, SLOT(close())); + + _setBackColorAction = _GraphicsViewport->createSetBackgroundColor(this); + _setBackColorAction->setText(tr("Set &background color")); + _setBackColorAction->setIcon(QIcon(":/images/ico_bgcolor.png")); + _setBackColorAction->setStatusTip(tr("Set background color")); + + _resetSceneAction = new QAction(tr("&Reset scene"), this); + _resetSceneAction->setStatusTip(tr("Reset current scene")); + connect(_resetSceneAction, SIGNAL(triggered()), this, SLOT(resetScene())); + + _saveScreenshotAction = _GraphicsViewport->createSaveScreenshotAction(this); + _saveScreenshotAction->setText(tr("Save &Screenshot")); + _saveScreenshotAction->setStatusTip(tr("Make a screenshot of the current viewport and save")); + + _settingsAction = new QAction(tr("&Settings"), this); + _settingsAction->setIcon(QIcon(":/images/preferences.png")); + _settingsAction->setStatusTip(tr("Settings")); + connect(_settingsAction, SIGNAL(triggered()), this, SLOT(settings())); + + _aboutAction = new QAction(tr("&About"), this); + _aboutAction->setStatusTip(tr("Show the application's About box")); + connect(_aboutAction, SIGNAL(triggered()), this, SLOT(about())); + + _aboutQtAction = new QAction(tr("About &Qt"), this); + _aboutQtAction->setStatusTip(tr("Show the Qt library's About box")); + connect(_aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); +} + +void CMainWindow::createMenus() +{ + _fileMenu = menuBar()->addMenu(tr("&File")); + _fileMenu->addAction(_openAction); + _fileMenu->addSeparator(); + _fileMenu->addAction(_exitAction); + + _viewMenu = menuBar()->addMenu(tr("&View")); + _viewMenu->addAction(_setBackColorAction); + _viewMenu->addAction(_SetupFog->toggleViewAction()); + + _sceneMenu = menuBar()->addMenu(tr("&Scene")); + _sceneMenu->addAction(_resetSceneAction); + _sceneMenu->addAction(_saveScreenshotAction); + + _toolsMenu = menuBar()->addMenu(tr("&Tools")); + + _toolsMenu->addAction(_AnimationDialog->toggleViewAction()); + _AnimationDialog->toggleViewAction()->setIcon(QIcon(":/images/anim.png")); + + _toolsMenu->addAction(_AnimationSetDialog->toggleViewAction()); + _AnimationSetDialog->toggleViewAction()->setIcon(QIcon(":/images/animset.png")); + + _toolsMenu->addAction(_SlotManagerDialog->toggleViewAction()); + _SlotManagerDialog->toggleViewAction()->setIcon(QIcon(":/images/mixer.png")); + + _toolsMenu->addAction(_ParticleControlDialog->toggleViewAction()); + _ParticleControlDialog->toggleViewAction()->setIcon(QIcon(":/images/pqrticles.png")); + + _toolsMenu->addAction(_DayNightDialog->toggleViewAction()); + _DayNightDialog->toggleViewAction()->setIcon(QIcon(":/images/dqynight.png")); + + _toolsMenu->addAction(_WaterPoolDialog->toggleViewAction()); + _WaterPoolDialog->toggleViewAction()->setIcon(QIcon(":/images/water.png")); + _WaterPoolDialog->toggleViewAction()->setEnabled(false); + + _toolsMenu->addAction(_VegetableDialog->toggleViewAction()); + _VegetableDialog->toggleViewAction()->setIcon(QIcon(":/images/veget.png")); + + _toolsMenu->addAction(_GlobalWindDialog->toggleViewAction()); + _GlobalWindDialog->toggleViewAction()->setIcon(QIcon(":/images/wind.png")); + + _toolsMenu->addAction(_SkeletonScaleDialog->toggleViewAction()); + _SkeletonScaleDialog->toggleViewAction()->setIcon(QIcon(":/images/ico_skelscale.png")); + + _toolsMenu->addAction(_SunColorDialog->toggleViewAction()); + + connect(_ParticleControlDialog->toggleViewAction(), SIGNAL(triggered(bool)), + _ParticleWorkspaceDialog, SLOT(setVisible(bool))); + + connect(_ParticleControlDialog->toggleViewAction(), SIGNAL(triggered(bool)), + _ParticleWorkspaceDialog->_PropertyDialog, SLOT(setVisible(bool))); + + _toolsMenu->addSeparator(); + + _toolsMenu->addAction(_settingsAction); + + menuBar()->addSeparator(); + + _helpMenu = menuBar()->addMenu(tr("&Help")); + _helpMenu->addAction(_aboutAction); + _helpMenu->addAction(_aboutQtAction); +} + +void CMainWindow::createToolBars() +{ + _fileToolBar = addToolBar(tr("&File")); + _fileToolBar->addAction(_openAction); + + //_editToolBar = addToolBar(tr("&Edit")); + //_editToolBar->addSeparator(); + _toolsBar = addToolBar(tr("&Tools")); + _toolsBar->addAction(_AnimationDialog->toggleViewAction()); + _toolsBar->addAction(_AnimationSetDialog->toggleViewAction()); + _toolsBar->addAction(_SlotManagerDialog->toggleViewAction()); + _toolsBar->addAction(_ParticleControlDialog->toggleViewAction()); + _toolsBar->addAction(_DayNightDialog->toggleViewAction()); + _toolsBar->addAction(_WaterPoolDialog->toggleViewAction()); + _toolsBar->addAction(_VegetableDialog->toggleViewAction()); + _toolsBar->addAction(_GlobalWindDialog->toggleViewAction()); + _toolsBar->addAction(_SkeletonScaleDialog->toggleViewAction()); +} + +void CMainWindow::createStatusBar() +{ + statusBar()->showMessage(tr("StatusReady")); +} + +void CMainWindow::createDialogs() +{ + // create animation dialog + _AnimationDialog = new CAnimationDialog(this); + addDockWidget(Qt::BottomDockWidgetArea, _AnimationDialog); + _AnimationDialog->setVisible(false); + + // create animation set manager dialog + _AnimationSetDialog = new CAnimationSetDialog(this); + addDockWidget(Qt::LeftDockWidgetArea, _AnimationSetDialog); + _AnimationSetDialog->setVisible(false); + + // create animation slot manager dialog + _SlotManagerDialog = new CSlotManagerDialog(this); + addDockWidget(Qt::RightDockWidgetArea, _SlotManagerDialog); + _SlotManagerDialog->setVisible(false); + + // create particle control dialog + _ParticleControlDialog = new CParticleControlDialog(_SkeletonTreeModel ,this); + addDockWidget(Qt::BottomDockWidgetArea, _ParticleControlDialog); + _ParticleControlDialog->setVisible(false); + + // create particle workspace dialog + _ParticleWorkspaceDialog = new CParticleWorkspaceDialog(this); + addDockWidget(Qt::LeftDockWidgetArea, _ParticleWorkspaceDialog); + _ParticleWorkspaceDialog->setVisible(false); + + // create day night (water) dialog + _DayNightDialog = new CDayNightDialog(this); + addDockWidget(Qt::BottomDockWidgetArea, _DayNightDialog); + _DayNightDialog->setVisible(false); + + // create water pool editor dialog + _WaterPoolDialog = new CWaterPoolDialog(this); + addDockWidget(Qt::RightDockWidgetArea, _WaterPoolDialog); + _WaterPoolDialog->setVisible(false); + + // create vegetable editor dialog + _VegetableDialog = new CVegetableDialog(this); + addDockWidget(Qt::RightDockWidgetArea, _VegetableDialog); + _VegetableDialog->setVisible(false); + + // create global wind power/direction dialog + _GlobalWindDialog = new CGlobalWindDialog(this); + addDockWidget(Qt::TopDockWidgetArea, _GlobalWindDialog); + _GlobalWindDialog->setVisible(false); + + // create sun color dialog + _SunColorDialog = new CSunColorDialog(this); + addDockWidget(Qt::LeftDockWidgetArea, _SunColorDialog); + _SunColorDialog->setVisible(false); + + // add property editor in widget area + addDockWidget(Qt::RightDockWidgetArea, _ParticleWorkspaceDialog->_PropertyDialog); + _ParticleWorkspaceDialog->_PropertyDialog->setVisible(false); + + // create skeleton scale dialog + _SkeletonScaleDialog = new CSkeletonScaleDialog(_SkeletonTreeModel, this); + addDockWidget(Qt::RightDockWidgetArea, _SkeletonScaleDialog); + _SkeletonScaleDialog->setVisible(false); + + // create setup fog dialog + _SetupFog = new CSetupFog(this); + addDockWidget(Qt::RightDockWidgetArea, _SetupFog); + _SetupFog->setVisible(false); + + connect(_ParticleControlDialog, SIGNAL(changeState()), _ParticleWorkspaceDialog, SLOT(setNewState())); + connect(_ParticleWorkspaceDialog, SIGNAL(changeActiveNode()), _ParticleControlDialog, SLOT(updateActiveNode())); + connect(_AnimationSetDialog->ui.setLengthPushButton, SIGNAL(clicked()), _AnimationDialog, SLOT(changeAnimLength())); + connect(_AnimationSetDialog, SIGNAL(changeCurrentShape(QString)), _SkeletonTreeModel, SLOT(rebuildModel())); + connect(_AnimationSetDialog, SIGNAL(changeCurrentShape(QString)), _SkeletonScaleDialog, SLOT(setCurrentShape(QString))); + connect(_AnimationSetDialog, SIGNAL(changeCurrentShape(QString)), _AnimationDialog, SLOT(setCurrentShape(QString))); + connect(_AnimationSetDialog, SIGNAL(changeCurrentShape(QString)), _SlotManagerDialog, SLOT(updateUiSlots())); + connect(_ParticleControlDialog, SIGNAL(changeAutoCount(bool)), _ParticleWorkspaceDialog->getPropertyDialog()->getLocatedPage(), SLOT(setDisabledCountPS(bool))); +} + +bool CMainWindow::loadFile(const QString &fileName, const QString &skelName) +{ + if (!Modules::objView().loadMesh(fileName.toStdString(), skelName.toStdString())) + { + statusBar()->showMessage(tr("Loading canceled"),2000); + return false; + } + statusBar()->showMessage(tr("File loaded"),2000); + return true; +} + +void CMainWindow::cfcbQtStyle(NLMISC::CConfigFile::CVar &var) +{ + QApplication::setStyle(QStyleFactory::create(var.asString().c_str())); +} + +void CMainWindow::cfcbQtPalette(NLMISC::CConfigFile::CVar &var) +{ + if (var.asBool()) QApplication::setPalette(QApplication::style()->standardPalette()); + else QApplication::setPalette(_originalPalette); +} + +void CMainWindow::cfcbSoundEnabled(NLMISC::CConfigFile::CVar &var) +{ + _isSoundEnabled = var.asBool(); // update loop inits +} + +void CMainWindow::updateRender() +{ + updateInitialization(isVisible()); + + if (isVisible()) + { + + // call all update functions + // 01. Update Utilities (configuration etc) + + // 02. Update Time (deltas) + // ... + + // 03. Update Receive (network, servertime, receive messages) + // ... + + // 04. Update Input (keyboard controls, etc) + if (_isGraphicsInitialized) + Modules::objView().updateInput(); + + // 05. Update Weather (sky, snow, wind, fog, sun) + // ... + + // 06. Update Entities (movement, do after possible tp from incoming messages etc) + // - Move other entities + // - Update self entity + // - Move bullets + // ... + + // 07. Update Landscape (async zone loading near entity) + + + // 08. Update Collisions (entities) + // - Update entities + // - Update move container (swap with Update entities? todo: check code!) + // - Update bullets + Modules::veget().update(); + + // 09. Update Animations (playlists) + // - Needs to be either before or after entities, not sure, + // there was a problem with wrong order a while ago!!! + Modules::objView().updateAnimatePS(); + + Modules::objView().updateAnimation(_AnimationDialog->getTime()); + + // 09.2 Update Particle system editor + Modules::psEdit().update(); + + // 10. Update Camera (depends on entities) + // ... + + // 11. Update Interface (login, ui, etc) + // ... + + // 12. Update Sound (sound driver) + if (_isSoundInitialized) + { + Modules::sound().setListenerMatrix(Modules::objView().get3dMouseListener()->getViewMatrix()); + Modules::sound().update(); + } + // 13. Update Send (network, send new position etc) + // ... + + // 14. Update Debug (stuff for dev) + // ... + + if (_isGraphicsInitialized && !Modules::objView().getDriver()->isLost()) + { + // 01. Render Driver (background color) + Modules::objView().renderDriver(); // clear all buffers + + // 02. Render Sky (sky scene) + // ... + + // 04. Render Scene (entity scene) + Modules::objView().renderScene(); + + // 05. Render Effects (flare) + // ... + + // 06. Render Interface 3D (player names) + // ... + + // 07. Render Debug 3D + // ... + + // 08. Render Interface 2D (chatboxes etc, optionally does have 3d) + // ... + + // 09. Render Debug 2D (stuff for dev) + Modules::objView().renderDebug2D(); + + // swap 3d buffers + Modules::objView().getDriver()->swapBuffers(); + } + } +} + + +} /* namespace NLQT */ + +/* end of file */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/main_window.h b/code/nel/tools/3d/object_viewer_qt/src/main_window.h new file mode 100644 index 000000000..31b279efe --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/main_window.h @@ -0,0 +1,136 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef MAIN_WINDOW_H +#define MAIN_WINDOW_H + +// STL includes + +// Qt includes +#include + +// NeL includes +#include + +// Project includes + +namespace NLMISC { + class CConfigFile; +} + +namespace NLQT { + +class CGraphicsViewport; +class CAnimationDialog; +class CAnimationSetDialog; +class CSlotManagerDialog; +class CParticleControlDialog; +class CParticleWorkspaceDialog; +class CSetupFog; +class CSkeletonScaleDialog; +class CSkeletonTreeModel; +class CWaterPoolDialog; +class CVegetableDialog; +class CGlobalWindDialog; +class CDayNightDialog; +class CSunColorDialog; + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = 0); + ~CMainWindow(); + + virtual void setVisible(bool visible); + + int getFrameRate(); + CSkeletonTreeModel *getSkeletonModel() const + { return _SkeletonTreeModel; } + +private Q_SLOTS: + void open(); + void resetScene(); + void settings(); + void about(); + void updateStatusBar(); + void updateRender(); + +private: + void createActions(); + void createMenus(); + void createToolBars(); + void createStatusBar(); + void createDialogs(); + + void updateInitialization(bool visible); + bool loadFile(const QString &fileName, const QString &skelName); + + void cfcbQtStyle(NLMISC::CConfigFile::CVar &var); + void cfcbQtPalette(NLMISC::CConfigFile::CVar &var); + void cfcbSoundEnabled(NLMISC::CConfigFile::CVar &var); + + bool _isGraphicsInitialized, _isGraphicsEnabled; + bool _isSoundInitialized, _isSoundEnabled; + bool _isLandscapeInitialized, _isLandscapeEnabled; + + CGraphicsViewport *_GraphicsViewport; + CAnimationDialog *_AnimationDialog; + CAnimationSetDialog *_AnimationSetDialog; + CSlotManagerDialog *_SlotManagerDialog; + CParticleControlDialog *_ParticleControlDialog; + CParticleWorkspaceDialog *_ParticleWorkspaceDialog; + CVegetableDialog *_VegetableDialog; + CWaterPoolDialog *_WaterPoolDialog; + CGlobalWindDialog *_GlobalWindDialog; + CSetupFog *_SetupFog; + CSkeletonScaleDialog *_SkeletonScaleDialog; + CDayNightDialog *_DayNightDialog; + CSunColorDialog *_SunColorDialog; + + CSkeletonTreeModel *_SkeletonTreeModel; + + QPalette _originalPalette; + QString _lastDir; + + QTimer *_mainTimer; + QTimer *_statusBarTimer; + + QMenu *_fileMenu; + QMenu *_viewMenu; + QMenu *_sceneMenu; + QMenu *_toolsMenu; + QMenu *_helpMenu; + QToolBar *_fileToolBar; + QToolBar *_editToolBar; + QToolBar *_toolsBar; + QAction *_openAction; + QAction *_exitAction; + QAction *_setBackColorAction; + QAction *_resetSceneAction; + QAction *_saveScreenshotAction; + QAction *_settingsAction; + QAction *_aboutAction; + QAction *_aboutQtAction; +};/* class CMainWindow */ + +} /* namespace NLQT */ + +#endif // MAIN_WINDOW_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/mesh_form.ui b/code/nel/tools/3d/object_viewer_qt/src/mesh_form.ui new file mode 100644 index 000000000..70b80360b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/mesh_form.ui @@ -0,0 +1,87 @@ + + + CMeshWidget + + + + 0 + 0 + 195 + 114 + + + + GroupBox + + + + + + + + + Shape: + + + + + + + true + + + + + + + Browse + + + + + + + Shape not loaded + + + false + + + false + + + 1 + + + + + + + + + + + + + Morph + + + + + + + false + + + Edit + + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/mesh_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/mesh_widget.cpp new file mode 100644 index 000000000..f84ff703c --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/mesh_widget.cpp @@ -0,0 +1,205 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" + +// Qt include +#include +#include +#include + +// NeL include +#include +#include +#include +#include + +// Project includes +#include "mesh_widget.h" +#include "morph_mesh_dialog.h" + +namespace NLQT { + +CMeshWidget::CMeshWidget(QWidget *parent) + : QGroupBox(parent) +{ + _ui.setupUi(this); + + connect(_ui.browsePushButton, SIGNAL(clicked(bool)), this, SLOT(browseShape())); + connect(_ui.editPushButton, SIGNAL(clicked(bool)), this, SLOT(editMorph())); + connect(_ui.morphCheckBox, SIGNAL(toggled(bool)), this, SLOT(setMorphMesh(bool))); +} + +CMeshWidget::~CMeshWidget() +{ +} + +void CMeshWidget::setCurrentShape(CWorkspaceNode *ownerNode, NL3D::CPSShapeParticle *sp) +{ + _Node = ownerNode; + _ShapeParticle = sp; + + if (!dynamic_cast(_ShapeParticle)) + { + _ui.groupBox->hide(); + _ui.browsePushButton->setEnabled(true); + _ui.meshLineEdit->setEnabled(true); + _ui.label->setEnabled(true); + _ui.meshLineEdit->setText(_ShapeParticle->getShape().c_str()); + } + else + { + _ui.groupBox->show(); + NL3D::CPSConstraintMesh *cm = NLMISC::safe_cast(_ShapeParticle); + if (cm->getNumShapes() > 1) + _ui.morphCheckBox->setChecked(true); + + updateForMorph(); + } +} + +void CMeshWidget::browseShape() +{ + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open NeL data file"), ".", + tr("NeL shape file (*.shape)")); + + setCursor(Qt::WaitCursor); + if (!fileName.isEmpty()) + { + NLMISC::CPath::addSearchPath(NLMISC::CFile::getPath(fileName.toStdString())); + try + { + std::string shapeName = NLMISC::CFile::getFilename(fileName.toStdString()); + _ShapeParticle->setShape(shapeName); + _ui.meshLineEdit->setText(shapeName.c_str()); + touchPSState(); + } + catch (NLMISC::Exception &e) + { + QMessageBox::critical(this, tr("Shape loading error"), e.what(), QMessageBox::Ok); + } + updateMeshErrorString(); + } + setCursor(Qt::ArrowCursor); +} + +void CMeshWidget::setMorphMesh(bool state) +{ + NL3D::CPSConstraintMesh *cm = NLMISC::safe_cast(_ShapeParticle); + if (!cm) return; + if (state != (cm->getNumShapes() > 1)) + { + if (state) + { + // morphing enabled + std::string currName[2] = { cm->getShape(), cm->getShape() }; + cm->setShapes(currName, 2); + } + else + { + // morphing disabled + std::string currName = cm->getShape(0); + cm->setShape(currName); + } + updateModifiedFlag(); + } + updateForMorph(); +} + +void CMeshWidget::editMorph() +{ + NL3D::CPSConstraintMesh *cm = NLMISC::safe_cast(_ShapeParticle); + CMorphMeshDialog *dialog = new CMorphMeshDialog(_Node, cm, this); + dialog->show(); + dialog->exec(); + delete dialog; + updateMeshErrorString(); +} + +void CMeshWidget::updateForMorph() +{ + NL3D::CPSConstraintMesh *cm = NLMISC::safe_cast(_ShapeParticle); + if (cm) + { + bool enable = cm->getNumShapes() > 1; + _ui.browsePushButton->setEnabled(!enable); + _ui.meshLineEdit->setEnabled(!enable); + _ui.label->setEnabled(!enable); + _ui.editPushButton->setEnabled(enable); + if (!enable) + _ui.meshLineEdit->setText(cm->getShape().c_str()); + else + _ui.meshLineEdit->setText(""); + } + updateMeshErrorString(); +} + +void CMeshWidget::updateMeshErrorString() +{ + _ui.infoLabel->setText(""); + NL3D::CPSConstraintMesh *cm = dynamic_cast(_ShapeParticle); + if (!cm) return; + // Update mesh error label + std::vector numVerts; + cm->getShapeNumVerts(numVerts); + if (numVerts.empty()) return; + if (numVerts.size() == 1) + _ui.infoLabel->setText(getShapeErrorString(numVerts[0])); + + else + { + // display error msg for morphed meshs + bool hasError = false; + for(uint k = 0; k < numVerts.size(); ++k) + { + if (numVerts[k] < 0) + { + hasError = true; + break; + } + } + if (hasError) + _ui.infoLabel->setText(tr("Error in morph meshes")); + } +} + +QString CMeshWidget::getShapeErrorString(sint errorCode) +{ + switch(errorCode) + { + case NL3D::CPSConstraintMesh::ShapeFileIsNotAMesh: return tr("Not a mesh"); + case NL3D::CPSConstraintMesh::ShapeFileNotLoaded: return tr("Shape not loaded"); + case NL3D::CPSConstraintMesh::ShapeHasTooMuchVertices: return tr("Too much vertices"); + default: + break; + }; + return QString(); +} + +void CMeshWidget::touchPSState() +{ + if (_Node && _Node->getPSModel()) + { + _Node->getPSModel()->touchTransparencyState(); + _Node->getPSModel()->touchLightableState(); + } +} + +} /* namespace NLQT */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/mesh_widget.h b/code/nel/tools/3d/object_viewer_qt/src/mesh_widget.h new file mode 100644 index 000000000..f0cf1b921 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/mesh_widget.h @@ -0,0 +1,76 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef MESH_WIDGET_H +#define MESH_WIDGET_H + +#include "ui_mesh_form.h" + +// STL includes + +// Qt includes + +// NeL includes + +// Project includes +#include "particle_node.h" + +namespace NL3D +{ + struct CPSShapeParticle; +} + +namespace NLQT { + +class CMeshWidget: public QGroupBox +{ + Q_OBJECT + +public: + CMeshWidget(QWidget *parent = 0); + ~CMeshWidget(); + + void setCurrentShape(CWorkspaceNode *ownerNode, NL3D::CPSShapeParticle *sp); + + QString getShapeErrorString(sint errorCode); + +private Q_SLOTS: + void browseShape(); + void setMorphMesh(bool state); + void editMorph(); + +private: + CWorkspaceNode *_Node; + + NL3D::CPSShapeParticle *_ShapeParticle; + + void updateForMorph(); + + void updateMeshErrorString(); + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + void touchPSState(); + + Ui::CMeshWidget _ui; +}; /* class CMeshWidget */ + +} /* namespace NLQT */ + +#endif // MESH_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/modules.cpp b/code/nel/tools/3d/object_viewer_qt/src/modules.cpp new file mode 100644 index 000000000..b473d4ba9 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/modules.cpp @@ -0,0 +1,52 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "modules.h" + +NLQT::CConfiguration *Modules::_configuration = NULL; +NLQT::CObjectViewer *Modules::_objectViewer = NULL; +NLQT::CMainWindow *Modules::_mainWindow = NULL; +NLQT::CParticleEditor *Modules::_particleEditor = NULL; +NLQT::CSoundSystem *Modules::_soundSystem = NULL; +NLQT::CVegetableEditor *Modules::_vegetableEditor = NULL; + +void Modules::init() +{ + if (_configuration == NULL) _configuration = new NLQT::CConfiguration; + config().init(); + + if (_objectViewer == NULL) _objectViewer = new NLQT::CObjectViewer; + if (_soundSystem == NULL) _soundSystem = new NLQT::CSoundSystem; + if (_particleEditor == NULL) _particleEditor = new NLQT::CParticleEditor; + if (_vegetableEditor == NULL) _vegetableEditor = new NLQT::CVegetableEditor; + if (_mainWindow == NULL) _mainWindow = new NLQT::CMainWindow; +} + +void Modules::release() +{ + delete _mainWindow; _mainWindow = NULL; + delete _particleEditor; _particleEditor = NULL; + delete _vegetableEditor; _vegetableEditor = NULL; + delete _soundSystem; _soundSystem = NULL; + delete _objectViewer; _objectViewer = NULL; + + config().release(); + delete _configuration; _configuration = NULL; +} diff --git a/code/nel/tools/3d/object_viewer_qt/src/modules.h b/code/nel/tools/3d/object_viewer_qt/src/modules.h new file mode 100644 index 000000000..2ddd8a7dc --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/modules.h @@ -0,0 +1,55 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef MODULES_H +#define MODULES_H + +#include "configuration.h" +#include "object_viewer.h" +#include "particle_editor.h" +#include "main_window.h" +#include "sound_system.h" +#include "vegetable_editor.h" + +/** +@class Modules +@brief Main modules aggregated all parts of the program. +*/ +class Modules +{ +public: + static void init(); + static void release(); + + static NLQT::CConfiguration &config() { return *_configuration; } + static NLQT::CObjectViewer &objView() { return *_objectViewer; } + static NLQT::CMainWindow &mainWin() { return *_mainWindow; } + static NLQT::CParticleEditor &psEdit() { return *_particleEditor; } + static NLQT::CSoundSystem &sound() { return *_soundSystem; } + static NLQT::CVegetableEditor &veget() { return *_vegetableEditor; } +private: + static NLQT::CConfiguration *_configuration; + static NLQT::CObjectViewer *_objectViewer; + static NLQT::CMainWindow *_mainWindow; + static NLQT::CParticleEditor *_particleEditor; + static NLQT::CSoundSystem *_soundSystem; + static NLQT::CVegetableEditor *_vegetableEditor; +}; + +#endif // MODULES_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.cpp new file mode 100644 index 000000000..f4cb868a4 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.cpp @@ -0,0 +1,241 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "morph_mesh_dialog.h" + +// Qt include +#include + +// NeL includes +#include "nel/3d/ps_mesh.h" +#include "nel/3d/particle_system_model.h" + +// Projects includes +#include "mesh_widget.h" + +namespace NLQT { + +CMorphMeshDialog::CMorphMeshDialog(CWorkspaceNode *ownerNode, NL3D::CPSConstraintMesh *cm, QWidget *parent) + : QDialog(parent), _Node(ownerNode), _CM(cm) +{ + _ui.setupUi(this); + + _ui.morphValueWidget->setRange(0, 10); + _ui.morphValueWidget->setWrapper(&_MorphSchemeWrapper); + _ui.morphValueWidget->setSchemeWrapper(&_MorphSchemeWrapper); + _ui.morphValueWidget->init(); + + _MorphSchemeWrapper.CM = _CM; + _ui.morphValueWidget->setWorkspaceNode(_Node); + _ui.morphValueWidget->updateUi(); + + updateMeshList(); + + _ui.infoLabel->setVisible(!_CM->isValidBuild()); + + connect(_ui.addPushButton, SIGNAL(clicked()), this, SLOT(add())); + connect(_ui.removePushButton, SIGNAL(clicked()), this, SLOT(remove())); + connect(_ui.insertPushButton, SIGNAL(clicked()), this, SLOT(insert())); + connect(_ui.changePushButton, SIGNAL(clicked()), this, SLOT(change())); + connect(_ui.upPushButton, SIGNAL(clicked()), this, SLOT(up())); + connect(_ui.downPushButton, SIGNAL(clicked()), this, SLOT(down())); +} + +CMorphMeshDialog::~CMorphMeshDialog() +{ +} + +void CMorphMeshDialog::updateMeshList() +{ + nlassert(_CM); + std::vector numVerts; + _CM->getShapeNumVerts(numVerts); + _ui.listWidget->clear(); + for (uint k = 0; k < _CM->getNumShapes(); ++k) + _ui.listWidget->addItem(getShapeDescStr(k, numVerts[k])); + + _ui.listWidget->setCurrentRow(0); + if (_CM->getNumShapes() < 2) + _ui.removePushButton->setEnabled(false); + else + _ui.removePushButton->setEnabled(true); +} + +QString CMorphMeshDialog::getShapeDescStr(uint shapeIndex, sint numVerts) const +{ + if (numVerts >= 0) + { + QString verts(tr("vertices")); + QString msg = _CM->getShape(shapeIndex).c_str() + tr(" (%1 vertices)").arg(numVerts); + return msg; + } + else + { + QString error = qobject_cast(QObject::parent())->getShapeErrorString(numVerts); + QString result = _CM->getShape(shapeIndex).c_str() + QString(" (%1)").arg(error); + return result; + } +} + +void CMorphMeshDialog::add() +{ + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open NeL data file"), ".", + tr("NeL shape files (*.shape)")); + + setCursor(Qt::WaitCursor); + if (!fileName.isEmpty()) + { + std::vector shapeNames; + shapeNames.resize(_CM->getNumShapes() + 1); + _CM->getShapesNames(&shapeNames[0]); + uint index = (uint)shapeNames.size() - 1; + shapeNames[index] = fileName.toStdString(); + _CM->setShapes(&shapeNames[0], (uint)shapeNames.size()); + std::vector numVerts; + _CM->getShapeNumVerts(numVerts); + _ui.listWidget->addItem(getShapeDescStr(index, numVerts[index])); + _ui.removePushButton->setEnabled(true); + touchPSState(); + } + setCursor(Qt::ArrowCursor); + _ui.infoLabel->setVisible(!_CM->isValidBuild()); +} + +void CMorphMeshDialog::remove() +{ + sint row = _ui.listWidget->currentRow(); + std::vector shapeNames; + shapeNames.resize(_CM->getNumShapes()); + _CM->getShapesNames(&shapeNames[0]); + shapeNames.erase(shapeNames.begin() + row); + _CM->setShapes(&shapeNames[0], (uint)shapeNames.size()); + + if (_CM->getNumShapes() < 2) + _ui.removePushButton->setEnabled(false); + + touchPSState(); + updateMeshList(); + _ui.infoLabel->setVisible(!_CM->isValidBuild()); +} + +void CMorphMeshDialog::insert() +{ + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open NeL data file"), ".", + tr("NeL shape files (*.shape)")); + + setCursor(Qt::WaitCursor); + if (!fileName.isEmpty()) + { + sint row = _ui.listWidget->currentRow(); + std::vector shapeNames; + shapeNames.resize(_CM->getNumShapes()); + _CM->getShapesNames(&shapeNames[0]); + shapeNames.insert(shapeNames.begin() + row, fileName.toStdString()); + _CM->setShapes(&shapeNames[0], (uint)shapeNames.size()); + touchPSState(); + updateMeshList(); + _ui.listWidget->setCurrentRow(row); + } + setCursor(Qt::ArrowCursor); + _ui.infoLabel->setVisible(!_CM->isValidBuild()); +} + +void CMorphMeshDialog::change() +{ + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open NeL data file"), ".", + tr("NeL shape files (*.shape)")); + + setCursor(Qt::WaitCursor); + if (!fileName.isEmpty()) + { + sint row = _ui.listWidget->currentRow(); + _CM->setShape(row, fileName.toStdString()); + updateMeshList(); + touchPSState(); + } + setCursor(Qt::ArrowCursor); + _ui.infoLabel->setVisible(!_CM->isValidBuild()); +} + +void CMorphMeshDialog::up() +{ + sint row = _ui.listWidget->currentRow(); + if (row == 0) return; + std::vector shapeNames; + shapeNames.resize(_CM->getNumShapes()); + _CM->getShapesNames(&shapeNames[0]); + std::swap(shapeNames[row - 1], shapeNames[row]); + _CM->setShapes(&shapeNames[0], (uint)shapeNames.size()); + updateMeshList(); + _ui.listWidget->setCurrentRow(row - 1); + _ui.infoLabel->setVisible(!_CM->isValidBuild()); +} + +void CMorphMeshDialog::down() +{ + sint row = _ui.listWidget->currentRow(); + if (row == (sint) (_CM->getNumShapes() - 1)) return; + std::vector shapeNames; + shapeNames.resize(_CM->getNumShapes()); + _CM->getShapesNames(&shapeNames[0]); + std::swap(shapeNames[row + 1], shapeNames[row]); + _CM->setShapes(&shapeNames[0], (uint)shapeNames.size()); + updateMeshList(); + _ui.listWidget->setCurrentRow(row + 1); + _ui.infoLabel->setVisible(!_CM->isValidBuild()); +} + +void CMorphMeshDialog::touchPSState() +{ + if (_Node && _Node->getPSModel()) + { + _Node->getPSModel()->touchTransparencyState(); + _Node->getPSModel()->touchLightableState(); + } +} + +float CMorphMeshDialog::CMorphSchemeWrapper::get(void) const +{ + nlassert(CM); + return CM->getMorphValue(); +} + +void CMorphMeshDialog::CMorphSchemeWrapper::set(const float &v) +{ + nlassert(CM); + CM->setMorphValue(v); +} + +CMorphMeshDialog::CMorphSchemeWrapper::scheme_type *CMorphMeshDialog::CMorphSchemeWrapper::getScheme(void) const +{ + nlassert(CM); + return CM->getMorphScheme(); +} + +void CMorphMeshDialog::CMorphSchemeWrapper::setScheme(scheme_type *s) +{ + nlassert(CM); + CM->setMorphScheme(s); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.h new file mode 100644 index 000000000..8b700932d --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_dialog.h @@ -0,0 +1,85 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef MORPH_MESH_DIALOG_H +#define MORPH_MESH_DIALOG_H + +#include "ui_morph_mesh_form.h" + +// STL includes + +// Qt includes + +// NeL includes + +// Project includes +#include "ps_wrapper.h" + +namespace NL3D +{ + class CPSConstraintMesh; +} + +namespace NLQT { + +class CMorphMeshDialog: public QDialog +{ + Q_OBJECT + +public: + CMorphMeshDialog(CWorkspaceNode *ownerNode, NL3D::CPSConstraintMesh *cm, QWidget *parent = 0); + ~CMorphMeshDialog(); + +private Q_SLOTS: + void add(); + void remove(); + void insert(); + void change(); + void up(); + void down(); + +private: + /// fill the mesh list with the mesh names in the object being edited + void updateMeshList(); + + QString getShapeDescStr(uint shapeIndex, sint numVerts) const; + + /// wrapper for the morph scheme + struct CMorphSchemeWrapper : IPSSchemeWrapperFloat, IPSWrapperFloat + { + NL3D::CPSConstraintMesh *CM; + virtual float get(void) const; + virtual void set(const float &); + virtual scheme_type *getScheme(void) const; + virtual void setScheme(scheme_type *s); + } _MorphSchemeWrapper; + + void touchPSState(); + + CWorkspaceNode *_Node; + + // the constraint mesh being edited + NL3D::CPSConstraintMesh *_CM; + + Ui::CMorphMeshDialog _ui; +}; /* class CMorphMeshDialog */ + +} /* namespace NLQT */ + +#endif // MORPH_MESH_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_form.ui b/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_form.ui new file mode 100644 index 000000000..aa6992d93 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/morph_mesh_form.ui @@ -0,0 +1,256 @@ + + + CMorphMeshDialog + + + + 0 + 0 + 413 + 516 + + + + Morph mesh edit + + + true + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Add + + + + :/images/list-add.png:/images/list-add.png + + + + + + + Change + + + + :/images/refresh.png:/images/refresh.png + + + + + + + Insert + + + + + + + Remove + + + + :/images/list-remove.png:/images/list-remove.png + + + + + + + Up + + + + :/images/go-up.png:/images/go-up.png + + + + + + + Down + + + + :/images/go-down.png:/images/go-down.png + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + 192 + 0 + 0 + + + + + + + + + 192 + 0 + 0 + + + + + + + + + 146 + 145 + 144 + + + + + + + + Warning: Shapes not found and incompatibles !! + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + NLQT::CAttribFloatWidget + QGroupBox +

attrib_widget.h
+ 1 + + + + + + + + buttonBox + rejected() + CMorphMeshDialog + reject() + + + 440 + 420 + + + 286 + 274 + + + + + buttonBox + accepted() + CMorphMeshDialog + accept() + + + 440 + 414 + + + 157 + 274 + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.cpp new file mode 100644 index 000000000..28168a0df --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.cpp @@ -0,0 +1,197 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "multi_tex_dialog.h" + +// NeL includes +#include "nel/3d/ps_particle_basic.h" +#include "nel/3d/texture_bump.h" + +// Project includes +#include "particle_node.h" + +namespace NLQT { + +CMultiTexDialog::CMultiTexDialog(CWorkspaceNode *ownerNode, NL3D::CPSMultiTexturedParticle *mtp, QWidget *parent) + : QDialog(parent), _Node(ownerNode), _MTP(mtp) +{ + _ui.setupUi(this); + + nlassert(_MTP); + + _ui.basicCapsCheckBox->setChecked(NL3D::CPSMultiTexturedParticle::areBasicCapsForced()); + _ui.useParticleDataCheckBox->setChecked(_MTP->getUseLocalDateAlt()); + _ui.useParticleDataCheckBox_2->setChecked(_MTP->getUseLocalDate()); + + bool bEnvBumpMapUsed = _MTP->getMainTexOp() == NL3D::CPSMultiTexturedParticle::EnvBumpMap ? true : false; + _ui.bumpFactorLabel->setEnabled(bEnvBumpMapUsed); + _ui.bumpFactorDoubleSpinBox->setEnabled(bEnvBumpMapUsed); + + _TexWrapper.OwnerNode = _Node; + _AlternateTexWrapper.OwnerNode = _Node; + _TexWrapper.MTP = _MTP; + _AlternateTexWrapper.MTP = _MTP; + + _ui.texWidget->setWrapper(&_TexWrapper); + _ui.texWidget->updateUi(); + + _ui.texWidget_2->setWrapper(&_AlternateTexWrapper); + _ui.texWidget_2->updateUi(); + + readValues(); + + _ui.enableAlternateCheckBox->setChecked(_MTP->isAlternateTexEnabled()); + _ui.alternateTab->setEnabled(_MTP->isAlternateTexEnabled()); + + _ui.texOpComboBox->setCurrentIndex(int(_MTP->getMainTexOp())); + _ui.texOpComboBox_2->setCurrentIndex(int(_MTP->getAlternateTexOp())); + + connect(_ui.bumpFactorDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(updateValues())); + connect(_ui.enableAlternateCheckBox, SIGNAL(toggled(bool)), this, SLOT(setEnabledAlternate(bool))); + connect(_ui.basicCapsCheckBox, SIGNAL(toggled(bool)), this, SLOT(setForceBasicCaps(bool))); + connect(_ui.texOpComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setMainOp(int))); + connect(_ui.texOpComboBox_2, SIGNAL(currentIndexChanged(int)), this, SLOT(setAlternateOp(int))); + connect(_ui.useParticleDataCheckBox, SIGNAL(toggled(bool)), this, SLOT(setUseParticleDate(bool))); + connect(_ui.useParticleDataCheckBox_2, SIGNAL(toggled(bool)), this, SLOT(setUseParticleDateAlt(bool))); + connect(_ui.uSpeed1DoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(updateValues())); + connect(_ui.vSpeed1DoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(updateValues())); + connect(_ui.uSpeed2DoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(updateValues())); + connect(_ui.vSpeed2DoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(updateValues())); + connect(_ui.uSpeed1DoubleSpinBox_2, SIGNAL(valueChanged(double)), this, SLOT(updateValuesAlternate())); + connect(_ui.vSpeed1DoubleSpinBox_2, SIGNAL(valueChanged(double)), this, SLOT(updateValuesAlternate())); + connect(_ui.uSpeed2DoubleSpinBox_2, SIGNAL(valueChanged(double)), this, SLOT(updateValuesAlternate())); + connect(_ui.vSpeed2DoubleSpinBox_2, SIGNAL(valueChanged(double)), this, SLOT(updateValuesAlternate())); +} + +CMultiTexDialog::~CMultiTexDialog() +{ +} + +void CMultiTexDialog::setEnabledAlternate(bool state) +{ + if (state != _MTP->isAlternateTexEnabled()) + { + _MTP->enableAlternateTex(state); + updateModifiedFlag(); + } + _ui.alternateTab->setEnabled(state); +} + +void CMultiTexDialog::updateValues() +{ + NLMISC::CVector2f vs1, vs2; + + vs1.x = _ui.uSpeed1DoubleSpinBox->value(); + vs1.y = _ui.vSpeed1DoubleSpinBox->value(); + + vs2.x = _ui.uSpeed2DoubleSpinBox->value(); + vs2.y = _ui.vSpeed2DoubleSpinBox->value(); + + _MTP->setScrollSpeed(0, vs1); + _MTP->setScrollSpeed(1, vs2); + + _MTP->setBumpFactor(_ui.bumpFactorDoubleSpinBox->value()); + + updateModifiedFlag(); +} + +void CMultiTexDialog::updateValuesAlternate() +{ + NLMISC::CVector2f vs1, vs2; + + vs1.x = _ui.uSpeed1DoubleSpinBox_2->value(); + vs1.y = _ui.vSpeed1DoubleSpinBox_2->value(); + + vs2.x = _ui.uSpeed2DoubleSpinBox_2->value(); + vs2.y = _ui.vSpeed2DoubleSpinBox_2->value(); + + _MTP->setAlternateScrollSpeed(0, vs1); + _MTP->setAlternateScrollSpeed(1, vs2); + + updateModifiedFlag(); +} + +void CMultiTexDialog::setAlternateOp(int index) +{ + _MTP->setAlternateTexOp((NL3D::CPSMultiTexturedParticle::TOperator) index); + updateModifiedFlag(); +} + +void CMultiTexDialog::setMainOp(int index) +{ + _MTP->setMainTexOp((NL3D::CPSMultiTexturedParticle::TOperator) index); + updateModifiedFlag(); + + bool bEnvBumpMapUsed = _MTP->getMainTexOp() == NL3D::CPSMultiTexturedParticle::EnvBumpMap ? true : false; + _ui.bumpFactorLabel->setEnabled(bEnvBumpMapUsed); + _ui.bumpFactorDoubleSpinBox->setEnabled(bEnvBumpMapUsed); +} + +void CMultiTexDialog::setForceBasicCaps(bool state) +{ + NL3D::CPSMultiTexturedParticle::forceBasicCaps(state); +} + +void CMultiTexDialog::setUseParticleDate(bool state) +{ + _MTP->setUseLocalDate(state); + updateModifiedFlag(); +} + +void CMultiTexDialog::setUseParticleDateAlt(bool state) +{ + _MTP->setUseLocalDateAlt(state); + updateModifiedFlag(); +} + +void CMultiTexDialog::readValues() +{ + _ui.uSpeed1DoubleSpinBox->setValue(_MTP->getScrollSpeed(0).x); + _ui.vSpeed1DoubleSpinBox->setValue(_MTP->getScrollSpeed(0).y); + _ui.uSpeed2DoubleSpinBox->setValue(_MTP->getScrollSpeed(1).x); + _ui.vSpeed2DoubleSpinBox->setValue(_MTP->getScrollSpeed(1).y); + _ui.uSpeed1DoubleSpinBox_2->setValue(_MTP->getAlternateScrollSpeed(0).x); + _ui.vSpeed1DoubleSpinBox_2->setValue(_MTP->getAlternateScrollSpeed(0).y); + _ui.uSpeed2DoubleSpinBox_2->setValue(_MTP->getAlternateScrollSpeed(1).x); + _ui.vSpeed2DoubleSpinBox_2->setValue(_MTP->getAlternateScrollSpeed(1).y); + _ui.bumpFactorDoubleSpinBox->setValue(_MTP->getBumpFactor()); +} + +NL3D::ITexture *CMultiTexDialog::CMainTexWrapper::get(void) +{ + return MTP->getTexture2(); +} + +void CMultiTexDialog::CMainTexWrapper::set(NL3D::ITexture *tex) +{ + MTP->setTexture2(tex); +} + +NL3D::ITexture *CMultiTexDialog::CAlternateTexWrapper::get(void) +{ + return MTP->getTexture2Alternate(); +} + +void CMultiTexDialog::CAlternateTexWrapper::set(NL3D::ITexture *tex) +{ + MTP->setTexture2Alternate(tex); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.h new file mode 100644 index 000000000..1d4997e2b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/multi_tex_dialog.h @@ -0,0 +1,89 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef MULTI_TEX_DIALOG_H +#define MULTI_TEX_DIALOG_H + +#include "ui_multi_tex_form.h" + + +// STL includes + +// Qt includes + +// NeL includes + +// Project includes +#include "ps_wrapper.h" + +namespace NL3D +{ + class CPSMultiTexturedParticle; +} + +namespace NLQT { + +class CWorkspaceNode; + +class CMultiTexDialog: public QDialog +{ + Q_OBJECT + +public: + CMultiTexDialog(CWorkspaceNode *ownerNode, NL3D::CPSMultiTexturedParticle *mtp, QWidget *parent = 0); + ~CMultiTexDialog(); + +private Q_SLOTS: + void setEnabledAlternate(bool state); + void updateValues(); + void updateValuesAlternate(); + void setAlternateOp(int index); + void setMainOp(int index); + void setForceBasicCaps(bool state); + void setUseParticleDate(bool state); + void setUseParticleDateAlt(bool state); + +private: + struct CMainTexWrapper : IPSWrapperTexture + { + NL3D::CPSMultiTexturedParticle *MTP; + virtual NL3D::ITexture *get(void); + virtual void set(NL3D::ITexture *); + } _TexWrapper; + + struct CAlternateTexWrapper : IPSWrapperTexture + { + NL3D::CPSMultiTexturedParticle *MTP; + virtual NL3D::ITexture *get(void); + virtual void set(NL3D::ITexture *); + } _AlternateTexWrapper; + + CWorkspaceNode *_Node; + + NL3D::CPSMultiTexturedParticle *_MTP; + + void readValues(); + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + Ui::CMultiTexDialog _ui; +}; /* class CMultiTexDialog */ + +} /* namespace NLQT */ + +#endif // MULTI_TEX_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/multi_tex_form.ui b/code/nel/tools/3d/object_viewer_qt/src/multi_tex_form.ui new file mode 100644 index 000000000..55fb83375 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/multi_tex_form.ui @@ -0,0 +1,290 @@ + + + CMultiTexDialog + + + + 0 + 0 + 360 + 269 + + + + Multitexturing + + + true + + + + + + true + + + 0 + + + + Main tex + + + + + + U speed 1: + + + + + + + + + + + + + V speed 1: + + + + + + + + + + U speed 2: + + + + + + + + + + V speed 2: + + + + + + + + + + false + + + Bump factor: + + + + + + + false + + + + + + + Use particle data + + + + + + + Tex op: + + + + + + + + Add + + + + + Modulate + + + + + Decal + + + + + EnvBumpMap + + + + + + + + + false + + + Alternate tex + + + + + + U speed 1: + + + + + + + + + + + + + V speed 1: + + + + + + + + + + U speed 2: + + + + + + + + + + V speed 2: + + + + + + + + + + Tex op: + + + + + + + + Add + + + + + Modulate + + + + + Decal + + + + + + + + Use particle data all + + + + + + + + + + + Force basic caps + + + + + + + Enable alternate + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + NLQT::CParticleTextureWidget + QWidget +
particle_texture_widget.h
+ 1 +
+
+ + + + buttonBox + accepted() + CMultiTexDialog + accept() + + + 250 + 264 + + + 157 + 268 + + + + + buttonBox + rejected() + CMultiTexDialog + reject() + + + 276 + 264 + + + 286 + 268 + + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/object_viewer.cpp b/code/nel/tools/3d/object_viewer_qt/src/object_viewer.cpp new file mode 100644 index 000000000..db410ed43 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/object_viewer.cpp @@ -0,0 +1,452 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "object_viewer.h" + +// STL includes + +// NeL includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Project includes +#include "modules.h" +#include "configuration.h" + +using namespace std; +using namespace NLMISC; +using namespace NL3D; + +namespace NLQT { + +CObjectViewer::CObjectViewer() + : _Driver(NULL), _TextContext(NULL), + _phi(0), _psi(0),_dist(20), + _CameraFocal(75), + _CurrentInstance(""), + _BloomEffect(false) +{ + +} + +CObjectViewer::~CObjectViewer() +{ +} + +void CObjectViewer::init(nlWindow wnd, uint16 w, uint16 h) +{ + //H_AUTO2 + nldebug("CObjectViewert::init"); + + loadConfig(); + + // create the driver + nlassert(!_Driver); + + _Driver = UDriver::createDriver(0, _Direct3D, 0); + nlassert(_Driver); + + // initialize the window with config file values + _Driver->setDisplay(wnd, NL3D::UDriver::CMode(w, h, 32)); + + _Light = ULight::createLight(); + + // set mode of the light + _Light->setMode(ULight::DirectionalLight); + + // set position of the light + _Light->setPosition(CVector(-20.f, 30.f, 10.f)); + + // white light + _Light->setAmbiant(CRGBA(255, 255, 255)); + + // set and enable the light + _Driver->setLight(0, *_Light); + _Driver->enableLight(0); + + // Create a scene + _Scene = _Driver->createScene(true); + + _PlayListManager = _Scene->createPlayListManager(); + + _Scene->enableLightingSystem(true); + + // create the camera + UCamera camera = _Scene->getCam(); + camera.setTransformMode (UTransformable::DirectMatrix); + + setSizeViewport(w, h); + + // camera will look at entities + updateCamera(0,0,0); + + NLMISC::CVector hotSpot=NLMISC::CVector(0,0,0); + + _MouseListener = _Driver->create3dMouseListener(); + _MouseListener->setMatrix(Modules::objView().getScene()->getCam().getMatrix()); + _MouseListener->setFrustrum(Modules::objView().getScene()->getCam().getFrustum()); + _MouseListener->setHotSpot(hotSpot); + _MouseListener->setMouseMode(U3dMouseListener::edit3d); + + // set the cache size for the font manager(in bytes) + _Driver->setFontManagerMaxMemory(2097152); + + // create the text context + nlassert(!_TextContext); + _TextContext = _Driver->createTextContext(_FontName); + nlassert(_TextContext); + + NL3D::CBloomEffect::instance().setDriver(_Driver); + NL3D::CBloomEffect::instance().setScene(_Scene); + NL3D::CBloomEffect::instance().init(!_Direct3D); + NL3D::CBloomEffect::instance().setDensityBloom(Modules::config().getConfigFile().getVar("BloomDensity").asInt()); + NL3D::CBloomEffect::instance().setSquareBloom(Modules::config().getConfigFile().getVar("BloomSquare").asBool()); +} + +void CObjectViewer::release() +{ + //H_AUTO2 + nldebug("CObjectViewer::release"); + + saveConfig(); + + // release text context + nlassert(_TextContext); + _Driver->deleteTextContext(_TextContext); + _TextContext = NULL; + + _Driver->delete3dMouseListener(_MouseListener); + + // delete all entities + deleteEntities(); + + _Scene->deletePlayListManager(_PlayListManager); + + // delete the scene + _Driver->deleteScene(_Scene); + + // delete the light + delete _Light; + + // release driver + nlassert(_Driver); + _Driver->release(); + delete _Driver; + _Driver = NULL; +} + +void CObjectViewer::updateInput() +{ + _Driver->EventServer.pump(); + + // New matrix from camera + _Scene->getCam().setTransformMode(NL3D::UTransformable::DirectMatrix); + _Scene->getCam().setMatrix (_MouseListener->getViewMatrix()); +} + +void CObjectViewer::renderDriver() +{ + // Render the scene. + if((NL3D::CBloomEffect::instance().getDriver() != NULL) && (_BloomEffect)) + { + NL3D::CBloomEffect::instance().initBloom(); + } + _Driver->clearBuffers(_BackgroundColor); +} + +void CObjectViewer::renderScene() +{ + // render the scene + _Scene->render(); + + if((NL3D::CBloomEffect::instance().getDriver() != NULL) && (_BloomEffect)) + { + NL3D::CBloomEffect::instance().endBloom(); + NL3D::CBloomEffect::instance().endInterfacesDisplayBloom(); + } +} + +void CObjectViewer::renderDebug2D() +{ +} + +void CObjectViewer::saveScreenshot(const std::string &nameFile, bool jpg, bool png, bool tga) +{ + //H_AUTO2 + + // FIXME: create screenshot path if it doesn't exist! + + // empty bitmap + CBitmap bitmap; + // copy the driver buffer to the bitmap + _Driver->getBuffer(bitmap); + // create the file name + string filename = std::string("./") + nameFile; + // write the bitmap as a jpg, png or tga to the file + if (jpg) + { + string newfilename = CFile::findNewFile(filename + ".jpg"); + COFile outputFile(newfilename); + bitmap.writeJPG(outputFile, 100); + nlinfo("Screenshot '%s' saved", newfilename.c_str()); + } + if (png) + { + string newfilename = CFile::findNewFile(filename + ".png"); + COFile outputFile(newfilename); + bitmap.writePNG(outputFile, 24); + nlinfo("Screenshot '%s' saved", newfilename.c_str()); + } + if (tga) + { + string newfilename = CFile::findNewFile(filename + ".tga"); + COFile outputFile(newfilename); + bitmap.writeTGA(outputFile, 24, false); + nlinfo("Screenshot '%s' saved", newfilename.c_str()); + } +} + +bool CObjectViewer::loadMesh(const std::string &meshFileName, const std::string &skelFileName) +{ + std::string fileName = CFile::getFilenameWithoutExtension(meshFileName); + if ( _Entities.count(fileName) != 0) + return false; + + CPath::addSearchPath(CFile::getPath(meshFileName), false, false); + + // create instance of the mesh character + UInstance Entity = _Scene->createInstance(meshFileName); + + USkeleton Skeleton = _Scene->createSkeleton(skelFileName); + + // if we can't create entity, skip it + if (Entity.empty()) return false; + + // TODO: remade at typedef std::map CEntities; + EIT eit = (_Entities.insert (make_pair (fileName, CEntity()))).first; + CEntity &entity = (*eit).second; + + // set the entity up + entity._Name = fileName; + entity._FileNameShape = meshFileName; + entity._FileNameSkeleton = skelFileName; + entity._Instance = Entity; + if (!Skeleton.empty()) + { + entity._Skeleton = Skeleton; + entity._Skeleton.bindSkin (entity._Instance); + } + entity._AnimationSet = _Driver->createAnimationSet(false); + entity._PlayList = _PlayListManager->createPlayList(entity._AnimationSet); + return true; +} + +void CObjectViewer::resetScene() +{ + deleteEntities(); + + // Reset camera. + //.. + + // to load files with the same name but located in different directories + CPath::clearMap(); + + // load and set search paths from config + Modules::config().configSearchPaths(); + + _CurrentInstance = ""; + + nlinfo("Scene cleared"); +} + +void CObjectViewer::updateCamera(float deltaPsi, float deltaPhi, float deltaDist) +{ + _phi += deltaPhi; + _psi += deltaPsi; + _dist += deltaDist; + + if(_phi < -NLMISC::Pi/2) _phi -= deltaPhi; + if(_phi > NLMISC::Pi/2) _phi -= deltaPsi; + if (_dist < 1) _dist = 1; + + NLMISC::CQuat q0,q1,q2; + NLMISC::CVector up(0,0,1); + NLMISC::CVector v(0,0,1); + + q0.setAngleAxis(v,_psi); + v = NLMISC::CVector(0,1,0); + q1.setAngleAxis(v,_phi); + q2 = q0 * q1; + NLMISC::CMatrix m0; + m0.setRot(q2); + NLMISC::CVector camera = m0 * NLMISC::CVector(_dist,0,0); + + _Scene->getCam().lookAt(camera, up); +} + +void CObjectViewer::updateAnimatePS(uint64 deltaTime) +{ + static sint64 firstTime = NLMISC::CTime::getLocalTime(); + static sint64 lastTime = NLMISC::CTime::getLocalTime(); + if (deltaTime == 0) + { + deltaTime = NLMISC::CTime::getLocalTime() - lastTime; + } + lastTime += deltaTime; + float fdelta = 0.001f * (float) (lastTime - firstTime); + _Scene->animate ( fdelta); +} + +void CObjectViewer::updateAnimation(NL3D::TAnimationTime time) +{ + for (EIT eit = _Entities.begin(); eit != _Entities.end(); ++eit) + { + CEntity &entity = (*eit).second; + entity.update(time); + } + + // Animate scene animation + Modules::objView().getPlayListManager()->setup(time); +} + +void CObjectViewer::setBackgroundColor(NLMISC::CRGBA backgroundColor) +{ + _BackgroundColor = backgroundColor; + + // config file variable changes + Modules::config().getConfigFile().getVar("BackgroundColor").setAsInt(_BackgroundColor.R, 0); + Modules::config().getConfigFile().getVar("BackgroundColor").setAsInt(_BackgroundColor.G, 1); + Modules::config().getConfigFile().getVar("BackgroundColor").setAsInt(_BackgroundColor.B, 2); +} + +void CObjectViewer::setGraphicsDriver(bool Direct3D) +{ + _Direct3D = Direct3D; + + if (_Direct3D) Modules::config().getConfigFile().getVar("GraphicsDriver").setAsString("Direct3D"); + else Modules::config().getConfigFile().getVar("GraphicsDriver").setAsString("OpenGL"); +} + +void CObjectViewer::setSizeViewport(uint16 w, uint16 h) +{ + _Scene->getCam().setPerspective(_CameraFocal * (float)Pi/180.f, (float)w/h, 0.1f, 1000); +} + +void CObjectViewer::setCurrentObject(const std::string &name) +{ + if ((_Entities.count(name) != 0) || ( name.empty() )) _CurrentInstance = name; + else nlerror ("Entity %s not found", name.c_str()); + nlinfo("set current entity %s", _CurrentInstance.c_str()); +} + +CEntity& CObjectViewer::getEntity(const std::string &name) +{ + if ( _Entities.count(name) == 0) nlerror("Entity %s not found", name.c_str()); + EIT eit = _Entities.find (name); + return (*eit).second; +} + +void CObjectViewer::getListObjects(std::vector &listObj) +{ + listObj.clear(); + for (EIT eit = _Entities.begin(); eit != _Entities.end(); ++eit) + listObj.push_back((*eit).second._Name); +} + +void CObjectViewer::loadConfig() +{ + // set background color from config + Modules::config().setAndCallback("BackgroundColor", CConfigCallback(this, &CObjectViewer::cfcbBackgroundColor)); + + // set graphics driver from config + Modules::config().setAndCallback("GraphicsDriver",CConfigCallback(this,&CObjectViewer::cfcbGraphicsDriver)); + + Modules::config().setAndCallback("CameraFocal",CConfigCallback(this,&CObjectViewer::cfcbCameraFocal)); + + Modules::config().setAndCallback("FontName",CConfigCallback(this,&CObjectViewer::cfcbFontName)); + + Modules::config().setAndCallback("BloomEffect",CConfigCallback(this,&CObjectViewer::cfcbBloomEffect)); +} + +void CObjectViewer::saveConfig() +{ + Modules::config().dropCallback("BackgroundColor"); + Modules::config().dropCallback("GraphicsDriver"); + Modules::config().dropCallback("CameraFocal"); + Modules::config().dropCallback("FontName"); + Modules::config().dropCallback("BloomEffect"); +} + +void CObjectViewer::deleteEntities() +{ + _Entities.clear(); +} + +void CObjectViewer::cfcbBackgroundColor(NLMISC::CConfigFile::CVar &var) +{ + // read variable from config file + _BackgroundColor = CRGBA(var.asInt(0), var.asInt(1), var.asInt(2)); +} + +void CObjectViewer::cfcbGraphicsDriver(NLMISC::CConfigFile::CVar &var) +{ + // Choose driver opengl to work correctly under Linux example + _Direct3D = false; //_Driver = OpenGL; + +#ifdef NL_OS_WINDOWS + std::string driver = var.asString(); + if (driver == "Direct3D") _Direct3D = true; //m_Driver = Direct3D; + else if (driver == "OpenGL") _Direct3D = false; //m_Driver = OpenGL; + else nlwarning("Invalid driver specified, defaulting to OpenGL"); +#endif +} + +void CObjectViewer::cfcbCameraFocal(NLMISC::CConfigFile::CVar &var) +{ + _CameraFocal = var.asFloat(); +} + +void CObjectViewer::cfcbFontName(NLMISC::CConfigFile::CVar &var) +{ + _FontName = var.asString(); +} + +void CObjectViewer::cfcbBloomEffect(NLMISC::CConfigFile::CVar &var) +{ + _BloomEffect = var.asBool(); +} + +} /* namespace NLQT */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/object_viewer.h b/code/nel/tools/3d/object_viewer_qt/src/object_viewer.h new file mode 100644 index 000000000..cdd10b648 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/object_viewer.h @@ -0,0 +1,227 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef OBJECT_VIEWER_H +#define OBJECT_VIEWER_H + +#include + +// STL includes +#include +#include + +// NeL includes +#include +#include +#include + +// Project includes +#include "entity.h" + +namespace NL3D { + class UDriver; + class UScene; + class ULight; + class UInstance; + class UCamera; + class USkeleton; + class UTextContext; + class UPlayListManager; + class U3dMouseListener; +} + +namespace NLQT { + +/** +@class CObjectViewer +@brief The class initializes the driver and creates a scene, provides basic control functions over the stage driver. +@details The class initializes the driver (by choosing OpenGL or Direct3D), and creates a scene (set an aspect), respectively +creates a light as well, load the font that is available further for +all other subsystems (eg: the signature of the coordinate axes) and Mouse Listener. +Settings are loaded from the configuration file. +Also, the class provides the following features to scene control: +- Loading and displaying of the shape, water shape and the particle system(loadMesh()). As well as the scene reset. (resetScene()) +- Provides access to a animation object (getEntity(), getListObjects()). +- Select of current object for various operation (mainly related to the animation and editing skeleton):setCurrentObject(), getCurrentObject(). +- Operations with the viewport, setting the correct perspective and creating of a screenshot. +- Function's updating keyboard and mouse (acts on the camera updateInput()), update(updateAnimatePS(), updateAnimation()) +and render the scene (renderDriver(), renderScene()). +- Provides access to a general NeL systems (getDriver(), getScene(), getPlayListManager(), getTextContext(), get3dMouseListener()). +*/ +class CObjectViewer +{ +public: + /// Default constructor. + CObjectViewer(); + + virtual ~CObjectViewer(); + + /// Init a driver and create scene. + /// @param wnd - handle window. + /// @param w - width window. + /// @param h - height window. + void init(nlWindow wnd, uint16 w, uint16 h); + + /// Release class. + void release(); + + /// Update mouse and keyboard events. And update camera matrix. + void updateInput(); + + /// Render Driver (clear all buffers and set background color). + void renderDriver(); + + /// Render current scene. + void renderScene(); + + /// Render Debug 2D (stuff for dev). + void renderDebug2D(); + + /// Make a screenshot of the current scene and save. + void saveScreenshot(const std::string &nameFile, bool jpg, bool png, bool tga); + + /// Load a mesh or particle system and add to current scene. + /// @param meshFileName - name loading shape or ps file. + /// @param skelFileName - name loading skeletin file. + /// @return true if file have been loaded, false if file have not been loaded. + bool loadMesh (const std::string &meshFileName, const std::string &skelFileName); + + /// Reset current scene. + void resetScene(); + + /// Update the navigation camera.(Note: deprecated) + /// @param deltaPsi - delta angle horizontal (radians). + /// @param deltaPhi - delta angle vertical (radians). + /// @param deltaDist - delta distance. + void updateCamera(float deltaPsi, float deltaPhi, float deltaDist); + + /// Update the animation time for Particle System animation. + /// @param deltaTime - set the manual animation time. + void updateAnimatePS(uint64 deltaTime = 0); + + /// Update animation from the scene + /// @param time - current time in second + void updateAnimation(NL3D::TAnimationTime time); + + /// Set the background color. + /// @param backgroundColor - background color. + void setBackgroundColor(NLMISC::CRGBA backgroundColor); + + /// Set type driver. + /// @param Direct3D - type driver (true - Direct3D) or (false -OpenGL) + void setGraphicsDriver(bool Direct3D); + + /// Set size viewport for correct set perspective + /// @param w - width window. + /// @param h - height window. + void setSizeViewport(uint16 w, uint16 h); + + void setBloomEffect(bool enabled) { _BloomEffect = enabled; } + + /// Select instance from the scene + /// @param name - name instance, "" if no instance edited + void setCurrentObject(const std::string &name); + + /// Get current instance from the scene + /// @return name current instance, "" if no instance edited + const std::string& getCurrentObject() { return _CurrentInstance; } + + /// Get entity from the scene + /// @return ref Entity + CEntity& getEntity(const std::string &name); + + /// Get full list instances from the scene + /// @param listObj - ref of return list instances + void getListObjects(std::vector &listObj); + + /// Get value background color. + /// @return background color. + inline NLMISC::CRGBA getBackgroundColor() const { return _BackgroundColor; } + + /// Get type driver. + /// @return true if have used Direct3D driver, false OpenGL driver. + inline bool getDirect3D() const { return _Direct3D; } + + inline bool getBloomEffect() const { return _BloomEffect; } + + /// Get a game interface for window driver. + /// @return pointer to the driver. + inline NL3D::UDriver *getDriver() const { return _Driver; } + + /// Get a game interface for scene. + /// @return pointer to the scene. + inline NL3D::UScene *getScene() const { return _Scene; } + + /// Get a manager of playlist + /// @return pointer to the UPlayListManager + inline NL3D::UPlayListManager *getPlayListManager() const { return _PlayListManager; } + + /// Get a game interface to render string + /// @return pointer to the UPlayListManager + inline NL3D::UTextContext *getTextContext() const { return _TextContext; } + + /// Get a 3d mouse listener + /// @return pointer to the U3dMouseListener + inline NL3D::U3dMouseListener *get3dMouseListener() const { return _MouseListener; } + +private: + void loadConfig(); + + void saveConfig(); + + // Delete all entities + void deleteEntities(); + + // Load background color from config file, intended for CConfiguration. + void cfcbBackgroundColor(NLMISC::CConfigFile::CVar &var); + void cfcbGraphicsDriver(NLMISC::CConfigFile::CVar &var); + void cfcbCameraFocal(NLMISC::CConfigFile::CVar &var); + void cfcbFontName(NLMISC::CConfigFile::CVar &var); + void cfcbBloomEffect(NLMISC::CConfigFile::CVar &var); + + NLMISC::CRGBA _BackgroundColor; + + NL3D::UDriver *_Driver; + NL3D::UScene *_Scene; + NL3D::UPlayListManager *_PlayListManager; + NL3D::ULight *_Light; + NL3D::UCamera *_Camera; + NL3D::UTextContext *_TextContext; + NL3D::U3dMouseListener *_MouseListener; + + // The entities storage + CEntities _Entities; + + /// Camera parameters. + float _phi, _psi, _dist; + + float _CameraFocal; + + std::string _FontName; + + bool _Direct3D; + bool _BloomEffect; + + std::string _CurrentInstance; + +};/* class CObjectViewer */ + +} /* namespace NLQT */ + +#endif // OBJECT_VIEWER_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/object_viewer_qt.qrc b/code/nel/tools/3d/object_viewer_qt/src/object_viewer_qt.qrc new file mode 100644 index 000000000..908cf78c2 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/object_viewer_qt.qrc @@ -0,0 +1,52 @@ + + + images/nel.png + images/open-file.png + images/go-down.png + images/go-up.png + images/list-add.png + images/list-remove.png + images/play.png + images/pause.png + images/stop.png + images/seek-backward.png + images/seek-forward.png + images/skip-backward.png + images/skip-forward.png + images/preferences.png + images/time.png + images/anim.png + images/animset.png + images/dqynight.png + images/mixer.png + images/pqrticles.png + images/sound.png + images/veget.png + images/water.png + images/wind.png + images/ico_bgcolor.png + images/ico_framedelay.png + images/ico_skelscale.png + images/clear.png + images/insert.png + images/new.png + images/refresh.png + images/save-as.png + images/save.png + images/insert-horizontal.png + + images/Workspace.bmp + images/ParticleSystem.bmp + images/Emitter.bmp + images/Sound.bmp + images/Light.bmp + images/Force.bmp + images/Particle.bmp + images/Located.bmp + images/LocatedInstance.bmp + images/CollisionZone.bmp + images/ParticleSystemNotLoaded.bmp + + images/nel_ide_load.png + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.cpp new file mode 100644 index 000000000..9756fb7b0 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.cpp @@ -0,0 +1,216 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_control_dialog.h" + +// Qt includes +#include + +// NeL includes +#include "nel/3d/scene.h" +#include + +// Project includes +#include "skeleton_tree_model.h" +#include "particle_link_skeleton_dialog.h" +#include "modules.h" + +namespace NLQT { + +CParticleControlDialog::CParticleControlDialog(CSkeletonTreeModel *model, QWidget *parent) + : QDockWidget(parent) +{ + _ui.setupUi(this); + + _timer = new QTimer(this); + + _particleLinkDialog = new CParticleLinkDialog(model, parent); + _particleLinkDialog->setVisible(false); + + _ui.numParticlesLabel->setMinimumWidth(_ui.numParticlesLabel->sizeHint().width()); + _ui.numWantedFacesLabel->setMinimumWidth(_ui.numWantedFacesLabel->sizeHint().width()); + _ui.systemTimesLabel->setMinimumWidth(_ui.systemTimesLabel->sizeHint().width()); + + stop(); + + connect(_timer, SIGNAL(timeout()), this, SLOT(updateCount())); + + connect(_ui.playToolButton, SIGNAL(clicked()), this, SLOT(play())); + connect(_ui.stopToolButton, SIGNAL(clicked()), this, SLOT(stop())); + connect(_ui.horizontalSlider, SIGNAL(sliderMoved(int)), this, SLOT(sliderMoved(int))); + connect(_ui.helpersCheckBox, SIGNAL(toggled(bool)), this, SLOT(displayHelpers(bool))); + connect(_ui.displayBoxCheckBox, SIGNAL(toggled(bool)), this, SLOT(displayBBox(bool))); + connect(_ui.loopCheckBox, SIGNAL(toggled(bool)), this, SLOT(autoRepeat(bool))); + connect(_ui.autoCountCheckBox, SIGNAL(toggled(bool)), this, SLOT(setEnabledAutoCount(bool))); + connect(_ui.resetAutoCountPushButton, SIGNAL(clicked()), this, SLOT(resetAutoCount())); + + connect(_ui.linkSkelPushButton, SIGNAL(clicked()), this, SLOT(linkSceleton())); + connect(_ui.unlinkSkelPushButton, SIGNAL(clicked()), this, SLOT(unlink())); + connect(_ui.setAnimPushButton, SIGNAL(clicked()), this, SLOT(setAnim())); + connect(_ui.clearAnimPushButton, SIGNAL(clicked()), this, SLOT(clearAnim())); + connect(_ui.restickPushButton, SIGNAL(clicked()), this, SLOT(restickObjects())); + + connect(_ui.generalToolButton, SIGNAL(clicked()), this, SLOT(setGeneralWidget())); + connect(_ui.additionalToolButton, SIGNAL(clicked()), this, SLOT(setAddtitionalWidget())); +} + +CParticleControlDialog::~CParticleControlDialog() +{ +} + +void CParticleControlDialog::updateActiveNode() +{ + _ui.skelLineEdit->setText(Modules::psEdit().getActiveNode()->getParentSkelName().c_str()); + _ui.animLineEdit->setText(Modules::psEdit().getActiveNode()->getTriggerAnim().c_str()); + _ui.autoCountCheckBox->setChecked(Modules::psEdit().getActiveNode()->getPSPointer()->getAutoCountFlag()); +} + +void CParticleControlDialog::play() +{ + if (Modules::psEdit().getActiveNode() == NULL) + { + _ui.playToolButton->setChecked(false); + return; + } + if (Modules::psEdit().isRunning()) + Modules::psEdit().pause(); + else if (_ui.multipleCheckBox->checkState() == Qt::Checked) + Modules::psEdit().startMultiple(); + else + Modules::psEdit().start(); + + _timer->start(200); + _ui.multipleCheckBox->setEnabled(false); + Q_EMIT changeState(); +} + +void CParticleControlDialog::stop() +{ + _timer->stop(); + _ui.playToolButton->setChecked(false); + Modules::psEdit().stop(); + _ui.multipleCheckBox->setEnabled(true); + + _ui.numParticlesLabel->setText(tr("Num particles:")); + _ui.numWantedFacesLabel->setText(tr("Num wanted faces:")); + _ui.systemTimesLabel->setText(tr("System time:")); + + Q_EMIT changeState(); +} + +void CParticleControlDialog::sliderMoved(int value) +{ + Modules::psEdit().setSpeed(float(value) / 100); +} + +void CParticleControlDialog::displayHelpers(bool state) +{ + Modules::psEdit().setDisplayHelpers(state); +} + +void CParticleControlDialog::displayBBox(bool state) +{ + Modules::psEdit().setDisplayBBox(state); +} + +void CParticleControlDialog::autoRepeat(bool state) +{ + Modules::psEdit().setAutoRepeat(state); +} + +void CParticleControlDialog::setEnabledAutoCount(bool state) +{ + resetAutoCount(); + Modules::psEdit().enableAutoCount(state); + _ui.resetAutoCountPushButton->setEnabled(state); + + Q_EMIT changeAutoCount(state); +} + +void CParticleControlDialog::resetAutoCount() +{ + stop(); + Modules::psEdit().resetAutoCount(Modules::psEdit().getActiveNode()); +} + +void CParticleControlDialog::updateCount() +{ + if (Modules::psEdit().getActiveNode() == NULL) + return; + + NL3D::CParticleSystem *ps = Modules::psEdit().getActiveNode()->getPSPointer(); + sint currNumParticles = (sint) ps->getCurrNumParticles(); + + // display number of particles for the currently active node + _ui.numParticlesLabel->setText(tr("Num particles: %1").arg(currNumParticles)); + + // display max number of wanted faces + NLMISC::CMatrix camMat = ps->getScene()->getCam()->getMatrix(); + sint numWantedFaces = (uint) ps->getWantedNumTris((ps->getSysMat().getPos() - camMat.getPos()).norm()); + _ui.numWantedFacesLabel->setText(tr("Num wanted faces: %1").arg(numWantedFaces)); + + // display system date + _ui.systemTimesLabel->setText(tr("System time: %1").arg(ps->getSystemDate())); + + Q_EMIT changeCount(); +} + +void CParticleControlDialog::linkSceleton() +{ + _particleLinkDialog->show(); +} + +void CParticleControlDialog::unlink() +{ + CWorkspaceNode *node = Modules::psEdit().getActiveNode(); + if (node == NULL) + return; + + node->unstickPSFromSkeleton(); +} + +void CParticleControlDialog::setAnim() +{ +} + +void CParticleControlDialog::clearAnim() +{ +} + +void CParticleControlDialog::restickObjects() +{ + CParticleWorkspace *pw = Modules::psEdit().getParticleWorkspace(); + if (pw == NULL) + return; + + pw->restickAllObjects(); +} + +void CParticleControlDialog::setGeneralWidget() +{ + _ui.stackedWidget->setCurrentWidget(_ui.pageGeneral); +} + +void CParticleControlDialog::setAddtitionalWidget() +{ + _ui.stackedWidget->setCurrentWidget(_ui.pageAdditional); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.h new file mode 100644 index 000000000..7715f307f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_control_dialog.h @@ -0,0 +1,79 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_CONTROL_DIALOG_H +#define PARTICLE_CONTROL_DIALOG_H + +#include +#include "ui_particle_control_form.h" + +// Project includes + +namespace NLQT { + +class CSkeletonTreeModel; +class CParticleLinkDialog; + +class CParticleControlDialog: public QDockWidget +{ + Q_OBJECT +public: + CParticleControlDialog(CSkeletonTreeModel *model, QWidget *parent = 0); + ~CParticleControlDialog(); + +Q_SIGNALS: + void changeState(); + void changeCount(); + void changeAutoCount(bool state); + +public Q_SLOTS: + void updateActiveNode(); + +private Q_SLOTS: + void play(); + void stop(); + void sliderMoved(int value); + void displayHelpers(bool state); + void displayBBox(bool state); + void autoRepeat(bool state); + void setEnabledAutoCount(bool state); + void resetAutoCount(); + void updateCount(); + + void linkSceleton(); + void unlink(); + void setAnim(); + void clearAnim(); + void restickObjects(); + + void setGeneralWidget(); + void setAddtitionalWidget(); + +private: + + QTimer *_timer; + + CParticleLinkDialog *_particleLinkDialog; + + Ui::CParticleControlDialog _ui; +}; /* class CParticleControlDialog */ + +} /* namespace NLQT */ + +#endif // PARTICLE_CONTROL_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_control_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_control_form.ui new file mode 100644 index 000000000..e85d030ff --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_control_form.ui @@ -0,0 +1,401 @@ + + + CParticleControlDialog + + + + 0 + 0 + 941 + 111 + + + + + 941 + 111 + + + + + 524287 + 524287 + + + + Particles system control + + + + + + + + 0 + 0 + + + + + 16777215 + 40 + + + + + + + + :/images/play.png + :/images/pause.png:/images/play.png + + + + 32 + 32 + + + + true + + + true + + + + + + + + 0 + 0 + + + + + 16777215 + 40 + + + + + + + + :/images/stop.png:/images/stop.png + + + + 32 + 32 + + + + true + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + + + 0 + 0 + + + + Loop + + + + + + + + 0 + 0 + + + + Display helpers + + + + + + + + 0 + 0 + + + + Enable auto count + + + + + + + + 0 + 0 + + + + Display box + + + + + + + + 0 + 0 + + + + All particle system + + + + + + + false + + + + 0 + 0 + + + + Reset auto count + + + + + + + + + + + + + + + false + + + Link play to scene play + + + + + + + true + + + Link to skeleton + + + false + + + + + + + true + + + Unlink + + + + + + + true + + + true + + + + + + + false + + + Restick all objects + + + + + + + false + + + Set anim + + + + + + + false + + + Clear anim + + + + + + + false + + + + + + + + + + + + + + + + 0 + 0 + + + + General + + + + + + + + 0 + 0 + + + + Additional + + + + + + + + + + 0 + 0 + + + + Num particles:9999999 + + + + + + + + 0 + 0 + + + + Num wanted faces:9999999 + + + + + + + + 0 + 0 + + + + System time:99999999999 + + + + + + + + 0 + 0 + + + + 100 + + + 100 + + + 100 + + + true + + + Qt::Horizontal + + + false + + + false + + + QSlider::TicksBelow + + + 20 + + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_editor.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_editor.cpp new file mode 100644 index 000000000..767694313 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_editor.cpp @@ -0,0 +1,575 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_editor.h" + +// NeL includes +#include +#include +#include +#include +#include +#include + +// Project includes +#include "modules.h" + +namespace NLQT { + +CParticleEditor::CParticleEditor(void): + _ActiveNode(NULL), _State(State::Stopped), _Speed(1.0f), + _AutoRepeat(false), _DisplayBBox(false), + _DisplayHelpers(false), _AutoUpdateBBox(false), + _EmptyBBox(true), _PW(NULL), + _Driver(NULL), _Scene(NULL), + _FontManager(NULL), _FontGen(NULL) +{ +} + +CParticleEditor::~CParticleEditor(void) +{ +} + +void CParticleEditor::init() +{ + NL3D::CDriverUser *driver = dynamic_cast(Modules::objView().getDriver()); + _Driver = driver->getDriver(); + + NL3D::CSceneUser *scene = dynamic_cast(Modules::objView().getScene()); + _Scene = &scene->getScene(); + + NL3D::CTextContextUser *textContext = dynamic_cast(Modules::objView().getTextContext()); + _FontManager = textContext->getTextContext().getFontManager(); + _FontGen = textContext->getTextContext().getFontGenerator(); + NL3D::CParticleSystem::setSerializeIdentifierFlag(true); +} + +void CParticleEditor::release() +{ + stop(); + closeWorkspace(); +} + +void CParticleEditor::setActiveNode(CWorkspaceNode *node) +{ + if (node == _ActiveNode) return; + _ActiveNode = node; + + bool wasRunning = _State == State::RunningSingle; + if (wasRunning) + { + stop(); + } + if (wasRunning && _ActiveNode) + { + start(); + } +} + +NL3D::CParticleSystemModel *CParticleEditor::getModelFromPS(NL3D::CParticleSystem *ps) const +{ + if (!ps) return NULL; + CWorkspaceNode *node = _PW->getNodeFromPS(ps); + if (!node) return NULL; + return node->getPSModel(); +} + +void CParticleEditor::loadWorkspace(const std::string &fullPath) +{ + // Add to the path + std::auto_ptr newPW(new CParticleWorkspace); + newPW->init(fullPath); + + // save empty workspace + try + { + newPW->load(); + } + catch(NLMISC::EStream &e) + { + nlerror(e.what()); + return; + } + + // try to load each ps + CWorkspaceNode *firstLoadedNode = NULL; + TPWNodeItr itr = newPW->getNodeList().begin(); + while(itr != newPW->getNodeList().end()) + { + CWorkspaceNode *node = (*itr); + try + { + node->loadPS(); + } + catch(NLMISC::EStream &e) + { + nlwarning(e.what()); + } + if (node->isLoaded() && !firstLoadedNode) + firstLoadedNode = node; + itr++; + } + closeWorkspace(); + _PW = newPW.release(); + setActiveNode(firstLoadedNode); +} + +void CParticleEditor::createNewWorkspace(const std::string &fullPath) +{ + CParticleWorkspace *newPW = new CParticleWorkspace; + newPW->init(fullPath); + // save empty workspace + try + { + newPW->save(); + } + catch(NLMISC::EStream &e) + { + nlerror(e.what()); + } + closeWorkspace(); + _PW = newPW; +} + +void CParticleEditor::saveWorkspaceStructure() +{ + nlassert(_PW); + try + { + _PW->save(); + } + catch(NLMISC::EStream &e) + { + nlerror(e.what()); + } +} + + +void CParticleEditor::saveWorkspaceContent() +{ + TPWNodeItr itr = _PW->getNodeList().begin(); + while(itr != _PW->getNodeList().end()) + { + CWorkspaceNode *node = (*itr); + if (node->isModified()) + node->savePS(); + node->setModified(false); + itr++; + } +} + +void CParticleEditor::closeWorkspace() +{ + setActiveNode(NULL); + delete _PW; + _PW = NULL; +} + +void CParticleEditor::start() +{ + switch(_State) + { + case State::Stopped: + if (_ActiveNode) + { + if (checkHasLoop(*_ActiveNode)) return; + play(*_ActiveNode); + nlassert(_PlayingNodes.empty()); + _PlayingNodes.push_back(_ActiveNode); + } + break; + case State::RunningSingle: + // no-op + return; + break; + case State::RunningMultiple: + stop(); + start(); + break; + case State::PausedSingle: + if (_ActiveNode) + { + unpause(*_ActiveNode); + } + break; + case State::PausedMultiple: + for(uint k = 0; k < _PlayingNodes.size(); ++k) + { + if (_PlayingNodes[k]) + { + unpause(*_PlayingNodes[k]); + } + } + stop(); + start(); + break; + default: + nlassert(0); + break; + } + _State = State::RunningSingle; +} + +void CParticleEditor::startMultiple() +{ + switch(_State) + { + case State::Stopped: + { + if (!_PW) return; + nlassert(_PlayingNodes.empty()); + TPWNodeItr itr = _PW->getNodeList().begin(); + while(itr != _PW->getNodeList().end()) + { + CWorkspaceNode *node = (*itr); + if (node->isLoaded()) + if (checkHasLoop(*node)) return; + itr++; + } + + itr = _PW->getNodeList().begin(); + while(itr != _PW->getNodeList().end()) + { + CWorkspaceNode *node = (*itr); + if (node->isLoaded()) + { + // really start the node only if there's no trigger anim + if (node->getTriggerAnim().empty()) + play(*node); + + _PlayingNodes.push_back(node); + } + itr++; + } + } + break; + case State::PausedSingle: + case State::RunningSingle: + stop(); + startMultiple(); + break; + case State::RunningMultiple: + // no-op + return; + break; + case State::PausedMultiple: + for(uint k = 0; k < _PlayingNodes.size(); ++k) + { + if (_PlayingNodes[k]) + { + unpause(*_PlayingNodes[k]); + } + } + break; + default: + nlassert(0); + break; + } + _State = State::RunningMultiple; +} + +void CParticleEditor::pause() +{ + switch(_State) + { + case State::Stopped: + // no-op + return; + case State::RunningSingle: + if (_ActiveNode) + { + pause(*_ActiveNode); + } + _State = State::PausedSingle; + break; + case State::RunningMultiple: + for(uint k = 0; k < _PlayingNodes.size(); ++k) + { + if (_PlayingNodes[k]) + { + pause(*_PlayingNodes[k]); + } + } + _State = State::PausedMultiple; + break; + case State::PausedSingle: + case State::PausedMultiple: + // no-op + return; + default: + nlassert(0); + break; + } +} + +void CParticleEditor::stop() +{ + switch(_State) + { + case State::Stopped: + // no-op + return; + case State::RunningSingle: + case State::RunningMultiple: + case State::PausedSingle: + case State::PausedMultiple: + for(uint k = 0; k < _PlayingNodes.size(); ++k) + { + if (_PlayingNodes[k]) + { + stop(*_PlayingNodes[k]); + } + } + _PlayingNodes.clear(); + break; + default: + nlassert(0); + break; + } + _State = State::Stopped; +} + +void CParticleEditor::update() +{ + if (!_ActiveNode) return; + if (_PW == NULL) return; + + NL3D::CParticleSystem *currPS = _ActiveNode->getPSPointer(); + + // compute BBox + if (_DisplayBBox) + { + if (_AutoUpdateBBox) + { + NLMISC::CAABBox currBBox; + currPS->forceComputeBBox(currBBox); + if (_EmptyBBox) + { + _EmptyBBox = false; + _CurrBBox = currBBox; + } + else + { + _CurrBBox = NLMISC::CAABBox::computeAABBoxUnion(currBBox, _CurrBBox); + } + currPS->setPrecomputedBBox(_CurrBBox); + } + } + + // auto repeat feature + if (_AutoRepeat) + { + if (isRunning()) + { + bool allFXFinished = true; + bool fxStarted = false; + TPWNodeItr itr = _PlayingNodes.begin(); + while(itr != _PlayingNodes.end()) + { + CWorkspaceNode *node = (*itr); + if (node->isLoaded()) + { + if (isRunning(node)) + { + fxStarted = true; + if (node->getPSPointer()->getSystemDate() <= node->getPSPointer()->evalDuration()) + { + allFXFinished = false; + break; + } + else + { + if (node->getPSPointer()->getCurrNumParticles() != 0) + { + allFXFinished = false; + break; + } + } + } + } + itr++; + } + if (fxStarted && allFXFinished) + restartAllFX(); + } + } + + // draw PSmodel + TPWNodeItr itr = _PW->getNodeList().begin(); + while(itr != _PW->getNodeList().end()) + { + CWorkspaceNode *node = (*itr); + if (node->isLoaded()) + { + if (node == _ActiveNode) + node->getPSModel()->enableDisplayTools(!isRunning(node) || _DisplayHelpers); + else + node->getPSModel()->enableDisplayTools(false); + + // hide / show the node + if (_State == State::RunningMultiple || _State == State::PausedMultiple) + { + if (isRunning(node)) + node->getPSModel()->show(); + else + node->getPSModel()->hide(); + } + else + { + if (node == _ActiveNode) + node->getPSModel()->show(); + else + node->getPSModel()->hide(); + } + } + itr++; + } +} + +void CParticleEditor::restartAllFX() +{ + if (_State == State::RunningMultiple || _State == State::PausedMultiple) + { + for(uint k = 0; k < _PlayingNodes.size(); ++k) + { + if (_PlayingNodes[k]) + { + stop(*_PlayingNodes[k]); + } + } + } + else + { + for(uint k = 0; k < _PlayingNodes.size(); ++k) + { + if (_PlayingNodes[k]) + { + stop(*_PlayingNodes[k]); + play(*_PlayingNodes[k]); + } + } + } +} + +void CParticleEditor::setSpeed(float value) +{ + _Speed = value; + + if (!isRunning()) return; + + TPWNodeItr itr = _PW->getNodeList().begin(); + while(itr != _PW->getNodeList().end()) + { + CWorkspaceNode *node = (*itr); + if (node->isLoaded()) + node->getPSModel()->setEllapsedTimeRatio(value); + itr++; + } +} + +void CParticleEditor::setDisplayBBox(bool enable) +{ + _DisplayBBox = enable; + NL3D::CParticleSystem::forceDisplayBBox(enable); +} + +void CParticleEditor::enableAutoCount(bool enable) +{ + if (!_ActiveNode) return; + if (enable == _ActiveNode->getPSPointer()->getAutoCountFlag()) return; + _ActiveNode->getPSPointer()->setAutoCountFlag(enable); + _ActiveNode->setModified(true); +} + + +void CParticleEditor::resetAutoCount(CWorkspaceNode *node, bool reset /* = true */) +{ + if (!node) return; + if (node->getResetAutoCountFlag() == reset) return; + node->setResetAutoCountFlag(reset); + node->setModified(true); +} + +bool CParticleEditor::isRunning(CWorkspaceNode *node) +{ + nlassert(node); + return node->isStateMemorized(); +} + +bool CParticleEditor::checkHasLoop(CWorkspaceNode &node) +{ + nlassert(node.isLoaded()); + if (!node.getPSPointer()->hasLoop()) return false; + return true; +} + +void CParticleEditor::play(CWorkspaceNode &node) +{ + if (isRunning(&node)) return; + // NB : node must be stopped, no check is done + nlassert(node.isLoaded()); + // if node not started, start it + node.memorizeState(); + // enable the system to take the right date from the scene + node.getPSModel()->enableAutoGetEllapsedTime(true); + node.getPSPointer()->setSystemDate(0.f); + node.getPSPointer()->reactivateSound(); + node.getPSModel()->activateEmitters(true); + if (node.getPSPointer()->getAutoCountFlag()) + { + if (node.getResetAutoCountFlag()) + { + // reset particle size arrays + node.getPSPointer()->matchArraySize(); + } + resetAutoCount(&node, false); + } + + // Set speed playback particle system + node.getPSModel()->setEllapsedTimeRatio(_Speed); +} + +void CParticleEditor::unpause(CWorkspaceNode &node) +{ + if (!isRunning(&node)) return; + nlassert(node.isLoaded()); + node.getPSModel()->enableAutoGetEllapsedTime(true); +} + +void CParticleEditor::pause(CWorkspaceNode &node) +{ + if (!isRunning(&node)) return; + nlassert(node.isLoaded()); + node.getPSModel()->enableAutoGetEllapsedTime(false); + node.getPSModel()->setEllapsedTime(0.f); +} + +void CParticleEditor::stop(CWorkspaceNode &node) +{ + if (!isRunning(&node)) return; + nlassert(node.isLoaded()); + node.restoreState(); + node.getPSModel()->enableAutoGetEllapsedTime(false); + node.getPSModel()->setEllapsedTime(0.f); + node.getPSModel()->activateEmitters(true); + node.getPSPointer()->stopSound(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_editor.h b/code/nel/tools/3d/object_viewer_qt/src/particle_editor.h new file mode 100644 index 000000000..98a20ff41 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_editor.h @@ -0,0 +1,220 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_EDITOR_H +#define PARTICLE_EDITOR_H + +#include + +// STL includes +#include +#include +#include + +// NeL includes +#include +#include + +// Projects includes +#include "particle_node.h" + +namespace NL3D { + class CParticleSystem; + class CParticleSystemModel; + class CShapeBank; + class CScene; + class IDriver; + class CFontManager; + class CFontGenerator; +} + +namespace NLQT { +/** +@class CParticleEditor +@brief The main class of the particles editor. +@details - Provides access to a container containing all of the particles systems (getParticleWorkspace()) +and also allows you to create an empty container (createNewWorkspace()) load / save from the file +and unload (loadWorkspace(), saveWorkspaceStructure(), saveWorkspaceContent()) +- Has basic operations management system to control a particles systems (start(), stop(), pause(), setSpeed() / setAutoRepeat()) +as in single mode, and in the multiple (start(), startMultiple()). +- Additional functions of particle systems: display / hide the assistanse elements (setDisplayHelpers()), +the calculation of bounding box (setAutoBBox(), getAutoBBox()), switching on the autoCount mode (setAutoBBox(), getAutoBBox()). +- Selection of the current system of particles for making various operations (setActiveNode(), getActiveNode()). +*/ +class CParticleEditor +{ +public: + struct State + { + enum List + { + Stopped = 1, + RunningSingle, + RunningMultiple, + PausedSingle, + PausedMultiple + }; + }; + CParticleEditor(void); + ~CParticleEditor(void); + + void init(); + + void release(); + + /// Active a new node of the workspace + /// Current active node is ready for edition. + /// Its bbox is displayed. + void setActiveNode(CWorkspaceNode *node); + + /// Get the node of the workspace that is currently active + CWorkspaceNode *getActiveNode() const { return _ActiveNode; } + + /// Get the particle system model that is currently active + NL3D::CParticleSystemModel *getActivePSM() const { return _ActiveNode ? _ActiveNode->getPSModel() : NULL; } + + /// Get a model from a ps pointer. The ps must belong to the workspace + NL3D::CParticleSystemModel *getModelFromPS(NL3D::CParticleSystem *ps) const; + + /// Load a new particle workspace (without asking if current workspace has been modified) + void loadWorkspace(const std::string &fullPath); + + void createNewWorkspace(const std::string &fullPath); + + /// Save the workspace structure + void saveWorkspaceStructure(); + + /// Save the workspace content + void saveWorkspaceContent(); + + /// Cloase the workspace without saving or asking the user + void closeWorkspace(); + + /// Force the active system to start + void start(); + + /// Start all systems + void startMultiple(); + + /// Pause the playing systems + void pause(); + + /// Force the playing systems to stop + void stop(); + + /// Update display of number of particles + void update(); + + /// Sets the animation speed particles system + void setSpeed(float value); + + void setDisplayBBox(bool enable); + + void setDisplayHelpers(bool enable) { _DisplayHelpers = enable; } + + void setAutoRepeat(bool enable) { _AutoRepeat = enable; } + + /// Auto bbox for fx + void setAutoBBox(bool enable) { _AutoUpdateBBox = enable; } + + bool getAutoBBox() const { return _AutoUpdateBBox; } + + /// Enable / disbale auto-count + void enableAutoCount(bool enable); + + /// Reset the autocount the next time the system will be started + void resetAutoCount(CWorkspaceNode *node, bool reset = true); + + /// Reset the auto compute bbox + void resetAutoBBox() { _EmptyBBox = true; } + + /// Get current state + int getState() const { return _State; } + + /// Return true if one or several system are being played + bool isRunning() const { return _State == State::RunningSingle || _State == State::RunningMultiple; } + + /// Return true if a system is paused. + /// Must call only if running + bool isPaused() const + { + return _State == State::PausedSingle || _State == State::PausedMultiple; + } + + CParticleWorkspace *getParticleWorkspace() const { return _PW; } + + NL3D::IDriver *getDriver() const { return _Driver; } + + NL3D::CScene *getScene() const { return _Scene; } + + /// Get the fontManager + NL3D::CFontManager *getFontManager() const {return _FontManager;} + + /// Get the fontGenerator + NL3D::CFontGenerator *getFontGenerator () const { return _FontGen; } + +private: + // Check if a node is inserted in the running list (it may be paused) + bool isRunning(CWorkspaceNode *node); + + // Check is a node has loops + bool checkHasLoop(CWorkspaceNode &node); + + void play(CWorkspaceNode &node); + void unpause(CWorkspaceNode &node); + void pause(CWorkspaceNode &node); + void stop(CWorkspaceNode &node); + void restartAllFX(); + + // Currently active node of the workspace + CWorkspaceNode *_ActiveNode; + + // List of fxs that are currently playing + TNodeVect _PlayingNodes; + + // Current state + int _State; + float _Speed; + bool _AutoRepeat; + bool _DisplayBBox; + bool _DisplayHelpers; + + // The system bbox must be updated automatically + bool _AutoUpdateBBox; + + // The last computed bbox for the system + bool _EmptyBBox; + NLMISC::CAABBox _CurrBBox; + + + CParticleWorkspace *_PW; + NL3D::IDriver *_Driver; + NL3D::CScene *_Scene; + + // Font manager + NL3D::CFontManager *_FontManager; + + // Font generator + NL3D::CFontGenerator *_FontGen; + +}; /* class CParticleEditor */ + +} /* namespace NLQT */ + +#endif // PARTICLE_EDITOR_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_force_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_force_form.ui new file mode 100644 index 000000000..e645fd3b0 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_force_form.ui @@ -0,0 +1,199 @@ + + + CForcePage + + + + 0 + 0 + 298 + 458 + + + + Form + + + + + + QTabWidget::East + + + 0 + + + + Target + + + + + + Targets: + + + + + + + Avaible targets: + + + + + + + + + + Qt::Vertical + + + + 20 + 57 + + + + + + + + + + + + 30 + 16777215 + + + + < + + + + + + + + 30 + 16777215 + + + + > + + + + + + + Qt::Vertical + + + + 20 + 56 + + + + + + + + + + + Parametric factor: + + + + + + + + + + Radial viscosity: + + + + + + + + + + Tangential viscosity: + + + + + + + + + + Qt::Vertical + + + + 20 + 36 + + + + + + + + + Force intensity + + + + + + + + + Qt::Vertical + + + + 20 + 349 + + + + + + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CDirectionWidget + QWidget +
direction_widget.h
+ 1 +
+ + NLQT::CAttribFloatWidget + QGroupBox +
attrib_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_force_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_force_page.cpp new file mode 100644 index 000000000..b2d516022 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_force_page.cpp @@ -0,0 +1,223 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_force_page.h" + +// Qt includes + #include + +// NeL includes +#include + +// Project includes + +namespace NLQT { + +CForcePage::CForcePage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.forceIntensityWidget->setRange(0, 10); + _ui.forceIntensityWidget->setWrapper(&_ForceIntensityWrapper); + _ui.forceIntensityWidget->setSchemeWrapper(&_ForceIntensityWrapper); + _ui.forceIntensityWidget->init(); + + _ui.parametricFactorWidget->setRange(0.0, 64.0); + _ui.parametricFactorWidget->setWrapper(&_ParamFactorWrapper); + + _ui.radialViscosityWidget->setRange(0.0, 1.0); + _ui.radialViscosityWidget->setWrapper(&_RadialViscosityWrapper); + + _ui.tangentialViscosityWidget->setRange(0, 1); + _ui.tangentialViscosityWidget->setWrapper(&_TangentialViscosityWrapper); + + _ui.directionWidget->setWrapper(&_DirectionWrapper); + + connect(_ui.toTargetsPushButton, SIGNAL(clicked()), this, SLOT(addTarget())); + connect(_ui.toAvaibleTargetsPushButton, SIGNAL(clicked()), this, SLOT(removeTarget())); +} + +CForcePage::~CForcePage() +{ +} + +void CForcePage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable) +{ + hideWrappersWidget(); + _Node = ownerNode; + _LBTarget = static_cast(locatedBindable); + + updateTargets(); + + // force with intensity case + if (dynamic_cast(_LBTarget)) + { + _ForceIntensityWrapper.F = dynamic_cast(_LBTarget); + + _ui.forceIntensityWidget->setWorkspaceNode(_Node); + _ui.forceIntensityWidget->updateUi(); + } + + // vortex (to tune viscosity) + if (dynamic_cast(_LBTarget)) + { + _RadialViscosityWrapper.OwnerNode = _Node; + _RadialViscosityWrapper.V = dynamic_cast(_LBTarget); + + _ui.radialViscosityWidget->updateUi(); + _ui.radialViscosityLabel->show(); + _ui.radialViscosityWidget->show(); + + _TangentialViscosityWrapper.OwnerNode = _Node; + _TangentialViscosityWrapper.V = dynamic_cast(_LBTarget); + + _ui.tangentialViscosityWidget->updateUi(); + _ui.tangentialViscosityLabel->show(); + _ui.tangentialViscosityWidget->show(); + } + + // deals with emitters that have a direction + if (dynamic_cast(_LBTarget)) + { + _DirectionWrapper.OwnerNode = _Node; + _DirectionWrapper.E = dynamic_cast(_LBTarget); + _ui.directionWidget->setDirectionWrapper(dynamic_cast(_LBTarget)); + + _ui.directionWidget->updateUi(); + _ui.directionWidget->show(); + } + + // Brownian (to tune parametric factor) + if (dynamic_cast(_LBTarget)) + { + _ParamFactorWrapper.OwnerNode = _Node; + _ParamFactorWrapper.F = static_cast(_LBTarget); + + _ui.parametricFactorWidget->updateUi(); + _ui.parametricFactorLabel->show(); + _ui.parametricFactorWidget->show(); + } +} + +void CForcePage::addTarget() +{ + // TODO: multiple add items + int totalCount = _ui.avaibleTargetsListWidget->count(); + if ((totalCount == 0) || (_ui.avaibleTargetsListWidget->currentRow() == -1)) return; + + CLocatedItem *item = dynamic_cast(_ui.avaibleTargetsListWidget->currentItem()); + + NL3D::CPSLocated *loc = item->getUserData(); + nlassert(loc); + + // check that force isn't applied on a forever lasting object + if (dynamic_cast(_LBTarget)) + { + if (loc->getLastForever()) + { + int ret = QMessageBox::warning(this, tr("NeL particle system editor"), + tr("The target object last forever. Applying a force on such an object may result in instability in the system after a while. " + "Continue ? (clue : you've been warned..)"), + QMessageBox::Ok | QMessageBox::Cancel); + + if (ret == QMessageBox::Cancel) + return; + } + + } + // + _LBTarget->attachTarget(loc); + + _ui.avaibleTargetsListWidget->takeItem(_ui.avaibleTargetsListWidget->currentRow()); + _ui.targetsListWidget->addItem(item); + + updateModifiedFlag(); +} + +void CForcePage::removeTarget() +{ + // TODO: multiple remove items + int totalCount = _ui.targetsListWidget->count(); + if ((totalCount == 0) || (_ui.targetsListWidget->currentRow() == -1)) return; + + CLocatedItem *item = dynamic_cast(_ui.targetsListWidget->takeItem(_ui.targetsListWidget->currentRow())); + + NL3D::CPSLocated *loc = item->getUserData(); + nlassert(loc); + + _LBTarget->detachTarget(loc); + + _ui.avaibleTargetsListWidget->addItem(item); + updateModifiedFlag(); +} + +void CForcePage::hideWrappersWidget() +{ + _ui.directionWidget->hide(); + _ui.parametricFactorLabel->hide(); + _ui.parametricFactorWidget->hide(); + _ui.radialViscosityLabel->hide(); + _ui.radialViscosityWidget->hide(); + _ui.tangentialViscosityLabel->hide(); + _ui.tangentialViscosityWidget->hide(); +} + +void CForcePage::updateTargets() +{ + uint k; + uint nbTarg = _LBTarget->getNbTargets(); + + _ui.targetsListWidget->clear(); + + std::set targetSet; + + // fill the box thta tells us what the target are + for(k = 0; k < nbTarg; ++k) + { + CLocatedItem *item = new CLocatedItem(QString(_LBTarget->getTarget(k)->getName().c_str()), + _ui.targetsListWidget); + item->setUserData(_LBTarget->getTarget(k)); + targetSet.insert(_LBTarget->getTarget(k)); + }; + + // fill abox with the available targets + NL3D::CParticleSystem *ps = _LBTarget->getOwner()->getOwner(); + + uint nbLocated = ps->getNbProcess(); + + _ui.avaibleTargetsListWidget->clear(); + + for (k = 0; k < nbLocated; ++k) + { + NL3D::CPSLocated *loc = dynamic_cast(ps->getProcess(k)); + if (loc) + { + if (targetSet.find(loc) == targetSet.end()) + { + CLocatedItem *item = new CLocatedItem(QString(loc->getName().c_str()), + _ui.avaibleTargetsListWidget); + item->setUserData(loc); + } + } + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_force_page.h b/code/nel/tools/3d/object_viewer_qt/src/particle_force_page.h new file mode 100644 index 000000000..80c1f541a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_force_page.h @@ -0,0 +1,137 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_FORCE_PAGE_H +#define PARTICLE_FORCE_PAGE_H + +#include +#include "ui_particle_force_form.h" + +// Qt includes + +// NeL includes +#include +#include + +// Project includes +#include "particle_node.h" +#include "ps_wrapper.h" + +namespace NLQT { + +/** +@class CLocatedItem +@brief Contain pointer to CPSLocated. +*/ +class CLocatedItem: public QListWidgetItem +{ +public: + CLocatedItem ( const QString & text, QListWidget * parent = 0, int type = UserType ): + QListWidgetItem(text, parent, type), _loc(NULL) {} + + void setUserData(NL3D::CPSLocated *loc) { _loc = loc;} + NL3D::CPSLocated *getUserData() const { return _loc;} + +private: + + NL3D::CPSLocated *_loc; +}; /* class CLocatedItem */ + +/** +@class CForcePage +@brief Page for QStackWidget, to edit forces in a particle system +*/ +class CForcePage: public QWidget +{ + Q_OBJECT + +public: + CForcePage(QWidget *parent = 0); + virtual ~CForcePage(); + + /// Set the force to edit. + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable); + +private Q_SLOTS: + void addTarget(); + void removeTarget(); + +private: + + /// wrapper to tune the intensity of a force + struct CForceIntensityWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSForceIntensity *F; + float get(void) const { return F->getIntensity(); } + void set(const float &value) {F->setIntensity(value); } + scheme_type *getScheme(void) const { return F->getIntensityScheme(); } + void setScheme(scheme_type *s) {F->setIntensityScheme(s); } + } _ForceIntensityWrapper; + + /// wrapper to tune the radial viscosity for vortices + struct CRadialViscosityWrapper : public IPSWrapperFloat + { + NL3D::CPSCylindricVortex *V; + float get(void) const { return V->getRadialViscosity(); } + void set(const float &value) { V->setRadialViscosity(value); } + } _RadialViscosityWrapper; + + /// wrapper to tune the tangential viscosity for vortices + struct CTangentialViscosityWrapper : public IPSWrapperFloat + { + NL3D::CPSCylindricVortex *V; + float get(void) const { return V->getTangentialViscosity(); } + void set(const float &value) { V->setTangentialViscosity(value); } + } _TangentialViscosityWrapper; + + /// wrappers to tune the direction + struct CDirectionWrapper : public IPSWrapper + { + NL3D::CPSDirection *E; + NLMISC::CVector get(void) const { return E->getDir(); } + void set(const NLMISC::CVector &d){ E->setDir(d); } + } _DirectionWrapper; + + /// wrappers to tune the parametric factor of brownian force + struct CParamFactorWrapper : public IPSWrapperFloat + { + NL3D::CPSBrownianForce *F; + float get(void) const { return F->getParametricFactor(); } + void set(const float &f){ F->setParametricFactor(f); } + } _ParamFactorWrapper; + + + void hideWrappersWidget(); + + void updateTargets(); + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + // the target we're focusing on + NL3D::CPSTargetLocatedBindable *_LBTarget; + + CWorkspaceNode *_Node; + + Ui::CForcePage _ui; + +}; /* class CForcePage */ + +} /* namespace NLQT */ + +#endif // PARTICLE_FORCE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_light_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_light_form.ui new file mode 100644 index 000000000..fbdf789b9 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_light_form.ui @@ -0,0 +1,114 @@ + + + CLightPage + + + + 0 + 0 + 317 + 364 + + + + Form + + + + + + QTabWidget::East + + + 0 + + + + Light color + + + + + + + + + Qt::Vertical + + + + 20 + 228 + + + + + + + + + Attenuation start + + + + + + + + + Qt::Vertical + + + + 20 + 228 + + + + + + + + + Attenuation end + + + + + + + + + Qt::Vertical + + + + 20 + 228 + + + + + + + + + + + + + NLQT::CAttribFloatWidget + QGroupBox +
attrib_widget.h
+ 1 +
+ + NLQT::CAttribRGBAWidget + QGroupBox +
attrib_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_light_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_light_page.cpp new file mode 100644 index 000000000..45bb1e63b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_light_page.cpp @@ -0,0 +1,74 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_light_page.h" + +// Qt includes + +// NeL includes + +// Project includes +#include "modules.h" + +namespace NLQT { + +CLightPage::CLightPage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.lightColorWidget->setWrapper(&_ColorWrapper); + _ui.lightColorWidget->setSchemeWrapper(&_ColorWrapper); + _ui.lightColorWidget->init(); + + _ui.attenStartWidget->setRange(0.01f, 5.f); + _ui.attenStartWidget->setWrapper(&_AttenStartWrapper); + _ui.attenStartWidget->setSchemeWrapper(&_AttenStartWrapper); + _ui.attenStartWidget->init(); + + _ui.attenEndWidget->setRange(0.01f, 5.f); + _ui.attenEndWidget->setWrapper(&_AttenEndWrapper); + _ui.attenEndWidget->setSchemeWrapper(&_AttenEndWrapper); + _ui.attenEndWidget->init(); +} + +CLightPage::~CLightPage() +{ +} + +void CLightPage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable) +{ + _Node = ownerNode; + _Light = static_cast(locatedBindable); + + _ColorWrapper.L = _Light; + _ui.lightColorWidget->setWorkspaceNode(_Node); + _ui.lightColorWidget->updateUi(); + + _AttenStartWrapper.L = _Light; + _ui.attenStartWidget->setWorkspaceNode(_Node); + _ui.attenStartWidget->updateUi(); + + _AttenEndWrapper.L = _Light; + _ui.attenEndWidget->setWorkspaceNode(_Node); + _ui.attenEndWidget->updateUi(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_light_page.h b/code/nel/tools/3d/object_viewer_qt/src/particle_light_page.h new file mode 100644 index 000000000..bdfaca7a7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_light_page.h @@ -0,0 +1,94 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_LIGHT_PAGE_H +#define PARTICLE_LIGHT_PAGE_H + +#include +#include "ui_particle_light_form.h" + +// STL includes + +// NeL includes +#include +#include "nel/3d/ps_light.h" + +// Project includes +#include "ps_wrapper.h" + +namespace NLQT { + +/** +@class CLightPage +@brief Page for QStackWidget, to edit lights in a particle system +*/ +class CLightPage: public QWidget +{ + Q_OBJECT + +public: + CLightPage(QWidget *parent = 0); + ~CLightPage(); + + /// Set the light to edit. + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable); + +private Q_SLOTS: +private: + + /// wrapper to set the color of light + struct CColorWrapper : public IPSWrapperRGBA, IPSSchemeWrapperRGBA + { + NL3D::CPSLight *L; + NLMISC::CRGBA get(void) const { return L->getColor(); } + void set(const NLMISC::CRGBA &v) { L->setColor(v); } + scheme_type *getScheme(void) const { return L->getColorScheme(); } + void setScheme(scheme_type *s) { L->setColorScheme(s); } + } _ColorWrapper; + + /// wrapper to set start atten radius + struct CAttenStartWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSLight *L; + float get(void) const { return L->getAttenStart(); } + void set(const float &v) { L->setAttenStart(v); } + scheme_type *getScheme(void) const { return L->getAttenStartScheme(); } + void setScheme(scheme_type *s) { L->setAttenStartScheme(s); } + } _AttenStartWrapper; + + /// wrapper to set end atten radius + struct CAttenEndWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSLight *L; + float get(void) const { return L->getAttenEnd(); } + void set(const float &v) { L->setAttenEnd(v); } + scheme_type *getScheme(void) const { return L->getAttenEndScheme(); } + void setScheme(scheme_type *s) { L->setAttenEndScheme(s); } + } _AttenEndWrapper; + + NL3D::CPSLight *_Light; + + CWorkspaceNode *_Node; + + Ui::CLightPage _ui; +}; /* class CLightPage */ + +} /* namespace NLQT */ + +#endif // PARTICLE_LIGHT_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.cpp new file mode 100644 index 000000000..003d89a50 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.cpp @@ -0,0 +1,93 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_link_skeleton_dialog.h" + +// Qt includes +#include + +// Project includes +#include "modules.h" +#include "particle_node.h" + +namespace NLQT { + +CParticleLinkDialog::CParticleLinkDialog(CSkeletonTreeModel *model, QWidget *parent) + : QDockWidget(parent) +{ + _ui.setupUi(this); + + qobject_cast< QMainWindow* >(parent)->addDockWidget(Qt::RightDockWidgetArea, this); + + _ui.treeView->setModel(model); + + connect(model, SIGNAL(modelReset()), this, SLOT(resetModel())); + connect(_ui.linkPushButton, SIGNAL(clicked()), this, SLOT(setLink())); + connect(_ui.unlinkPushButton, SIGNAL(clicked()), this, SLOT(setUnlink())); + connect(_ui.treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(clickedItem(QModelIndex))); +} + +CParticleLinkDialog::~CParticleLinkDialog() +{ +} + +void CParticleLinkDialog::setLink() +{ + CWorkspaceNode *node = Modules::psEdit().getActiveNode(); + if (node == NULL) + return; + + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + + CSkeletonTreeItem *item = static_cast(_ui.treeView->currentIndex().internalPointer()); + + NL3D::CSkeletonModel *skel = Modules::objView().getEntity(curObj).getSkeleton().getObjectPtr(); + uint boneIndex = item->getId(); + std::string parentSkelName = Modules::objView().getEntity(curObj).getFileNameSkeleton(); + std::string parentBoneName = skel->Bones[boneIndex].getBoneName(); + + node->stickPSToSkeleton(skel, boneIndex, parentSkelName, parentBoneName); +} + +void CParticleLinkDialog::setUnlink() +{ + CWorkspaceNode *node = Modules::psEdit().getActiveNode(); + if (node == NULL) + return; + + node->unstickPSFromSkeleton(); +} + +void CParticleLinkDialog::resetModel() +{ + _ui.treeView->expandAll(); + _ui.linkPushButton->setEnabled(false); + _ui.unlinkPushButton->setEnabled(false); +} + +void CParticleLinkDialog::clickedItem(const QModelIndex & index) +{ + _ui.linkPushButton->setEnabled(true); + _ui.unlinkPushButton->setEnabled(true); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.h new file mode 100644 index 000000000..5b7591ecd --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_dialog.h @@ -0,0 +1,57 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_LINK_SKELETON_DIALOG_H +#define PARTICLE_LINK_SKELETON_DIALOG_H + +#include "ui_particle_link_skeleton_form.h" + +// STL includes + +// NeL includes + +// Project includes +#include "skeleton_tree_model.h" + +namespace NLQT { + +class CParticleLinkDialog: public QDockWidget +{ + Q_OBJECT + +public: + CParticleLinkDialog(CSkeletonTreeModel *model, QWidget *parent = 0); + ~CParticleLinkDialog(); + +private Q_SLOTS: + void setLink(); + void setUnlink(); + void resetModel(); + void clickedItem(const QModelIndex & index); + +private: + + Ui::CParticleLinkDialog _ui; + +}; /* class CParticleLinkDialog */ + +} /* namespace NLQT */ + + +#endif // PARTICLE_LINK_SKELETON_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_form.ui new file mode 100644 index 000000000..4e629a6e5 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_link_skeleton_form.ui @@ -0,0 +1,50 @@ + + + CParticleLinkDialog + + + + 0 + 0 + 411 + 556 + + + + Link to skeleton + + + + + + + Choose bone to stick particle system + + + + + + + false + + + Link + + + + + + + false + + + Unlink + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_node.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_node.cpp new file mode 100644 index 000000000..357b1ac5d --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_node.cpp @@ -0,0 +1,523 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#include "stdpch.h" +#include "particle_node.h" + +// NeL includes +#include +#include +#include + +#include +#include +#include +#include + +// Project includes +#include "modules.h" + +using namespace NLMISC; +using namespace NL3D; + +namespace NLQT { + +CWorkspaceNode::CWorkspaceNode() +{ + _PS = NULL; + _PSM = NULL; + _ShapeBank = NULL; + _Modified = false; + _ParentSkel = NULL; + _ResetAutoCount = false; + _WS = NULL; +} + +CWorkspaceNode::~CWorkspaceNode() +{ + unload(); +} + +void CWorkspaceNode::memorizeState() +{ + nlassert(_WS); + if (!_PS) return; + _InitialPos.copySystemInitialPos(_PS); +} + +void CWorkspaceNode::restoreState() +{ + nlassert(_WS); + if (!_PS) return; + _InitialPos.restoreSystem(); +} + +bool CWorkspaceNode::isStateMemorized() const +{ + return _InitialPos.isStateMemorized(); +} + +void CWorkspaceNode::stickPSToSkeleton(NL3D::CSkeletonModel *skel, + uint bone, + const std::string &parentSkelName, + const std::string &parentBoneName) +{ + nlassert(_WS); + if (!_PSM) return; + unstickPSFromSkeleton(); + _ParentSkelName = parentSkelName; + _ParentBoneName = parentBoneName; + if (skel) + { + skel->stickObject(_PSM, bone); + _PSM->setMatrix(NLMISC::CMatrix::Identity); + _ParentSkel = skel; + } + if (_WS->getModificationCallback()) + { + _WS->getModificationCallback()->nodeSkelParentChanged(*this); + } +} + +void CWorkspaceNode::unstickPSFromSkeleton() +{ + nlassert(_WS); + _ParentSkelName = ""; + _ParentBoneName = ""; + if (!_PSM) return; + if (_ParentSkel) + { + _ParentSkel->detachSkeletonSon(_PSM); + _ParentSkel = NULL; + } +} + +void CWorkspaceNode::removeLocated(NL3D::CPSLocated *loc) +{ + nlassert(_WS); + if (_InitialPos.isStateMemorized()) + { + _InitialPos.removeLocated(loc); + } +} + +void CWorkspaceNode::removeLocatedBindable(NL3D::CPSLocatedBindable *lb) +{ + nlassert(_WS); + if (_InitialPos.isStateMemorized()) + { + _InitialPos.removeLocatedBindable(lb); + } +} + +void CWorkspaceNode::setModified(bool modified) +{ + nlassert(_WS); + if (_Modified == modified) return; + _Modified = modified; + _WS->nodeModified(*this); +} + +void CWorkspaceNode::unload() +{ + nlassert(_WS); + if (_PSM) + { + NL3D::CShapeBank *oldSB = Modules::psEdit().getScene()->getShapeBank(); + Modules::psEdit().getScene()->setShapeBank(_ShapeBank); + Modules::psEdit().getScene()->deleteInstance(_PSM); + Modules::psEdit().getScene()->setShapeBank(oldSB); + _PSM = NULL; + } + delete _ShapeBank; + _ShapeBank = NULL; + _PS = NULL; + _ParentSkel = NULL; +} + +void CWorkspaceNode::setup(NL3D::CParticleSystemModel &psm) +{ + nlassert(_WS); + psm.setTransformMode(NL3D::CTransform::DirectMatrix); + psm.setMatrix(NLMISC::CMatrix::Identity); + psm.setEditionMode(true); // this also force the system instanciation + for(uint k = 0; k < NL3D::MaxPSUserParam; ++k) + { + psm.bypassGlobalUserParamValue(k); + } + psm.enableAutoGetEllapsedTime(false); + psm.setEllapsedTime(0.f); // system is paused + // initialy, the ps is hidden + psm.hide(); + // link to the root for manipulation + // !!!!!!!!!!!!!!!!!!! + //_WS->getObjectViewer()->getSceneRoot()->hrcLinkSon(&psm); + NL3D::CParticleSystem *ps = psm.getPS(); + nlassert(ps); + ps->setFontManager(Modules::psEdit().getFontManager()); + ps->setFontGenerator(Modules::psEdit().getFontGenerator()); + ps->stopSound(); + // flush textures + psm.Shape->flushTextures(*Modules::psEdit().getDriver(), 0); +} + +void CWorkspaceNode::setTriggerAnim(const std::string &anim) +{ + nlassert(_WS); + if (anim == _TriggerAnim) return; + _WS->touch(); + _TriggerAnim = anim; +} + +void CWorkspaceNode::createEmptyPS() +{ + nlassert(_WS); + NL3D::CParticleSystem emptyPS; + NL3D::CParticleSystemShape *pss = new NL3D::CParticleSystemShape; + pss->buildFromPS(emptyPS); + std::auto_ptr sb(new NL3D::CShapeBank); + std::string shapeName = NLMISC::CFile::getFilename(_RelativePath); + sb->add(shapeName, pss); + NL3D::CShapeBank *oldSB = Modules::psEdit().getScene()->getShapeBank(); + Modules::psEdit().getScene()->setShapeBank(sb.get()); + NL3D::CParticleSystemModel *psm = NLMISC::safe_cast(Modules::psEdit().getScene()->createInstance(shapeName)); + nlassert(psm); + Modules::psEdit().getScene()->setShapeBank(oldSB); + setup(*psm); + unload(); + // commit new values + _PS = psm->getPS(); + _PSM = psm; + _ShapeBank = sb.release(); + _Modified = false; +} + +void CWorkspaceNode::init(CParticleWorkspace *ws) +{ + nlassert(ws); + _WS = ws; +} + +void CWorkspaceNode::setRelativePath(const std::string &relativePath) +{ + nlassert(_WS); + _RelativePath = relativePath; +} + +void CWorkspaceNode::serial(NLMISC::IStream &f) +{ + nlassert(_WS); + f.xmlPush("PROJECT_FILE"); + sint version = f.serialVersion(2); + f.xmlSerial(_RelativePath, "RELATIVE_PATH"); + if (version >= 1) + { + f.xmlSerial(_TriggerAnim, "TRIGGER_ANIMATION"); + } + if (version >= 2) + { + f.xmlSerial(_ParentSkelName, "PARENT_SKEL_NAME"); + f.xmlSerial(_ParentBoneName, "PARENT_BONE_NAME"); + } + f.xmlPop(); +} + +void CWorkspaceNode::savePS() throw(NLMISC::EStream) +{ + savePSAs(getFullPath()); +} + +void CWorkspaceNode::savePSAs(const std::string &fullPath) throw(NLMISC::EStream) +{ + nlassert(_WS); + if (!_PS) return; + // build a shape from our system, and save it + NL3D::CParticleSystemShape psc; + psc.buildFromPS(*_PS); + NL3D::CShapeStream st(&psc); + NLMISC::COFile oFile(fullPath); + oFile.serial(st); +} + +std::string CWorkspaceNode::getFullPath() const +{ + nlassert(_WS); + return _WS->getPath() + _RelativePath.c_str(); +} + +bool CWorkspaceNode::loadPS() throw(NLMISC::EStream) +{ + nlassert(_WS); + // manually load the PS shape (so that we can deal with exceptions) + NL3D::CShapeStream ss; + NLMISC::CIFile inputFile; + // collapse name + inputFile.open(getFullPath()); + ss.serial(inputFile); + std::auto_ptr sb(new NL3D::CShapeBank); + std::string shapeName = NLMISC::CFile::getFilename(_RelativePath); + sb->add(shapeName, ss.getShapePointer()); + NL3D::CShapeBank *oldSB = Modules::psEdit().getScene()->getShapeBank(); + Modules::psEdit().getScene()->setShapeBank(sb.get()); + NL3D::CTransformShape *trs = Modules::psEdit().getScene()->createInstance(shapeName); + if (!trs) + { + Modules::psEdit().getScene()->setShapeBank(oldSB); + return false; + } + NL3D::CParticleSystemModel *psm = dynamic_cast(trs); + if (!psm) + { + // Not a particle system + Modules::psEdit().getScene()->deleteInstance(trs); + return false; + } + Modules::psEdit().getScene()->setShapeBank(oldSB); + setup(*psm); + unload(); + // commit new values + _PS = psm->getPS(); + _PSM = psm; + _ShapeBank = sb.release(); + _Modified = false; + return true; +} + + +//*********************************************************************************************** +CParticleWorkspace::CParticleWorkspace() +{ + _Modified = false; + _ModificationCallback = NULL; +} + +CParticleWorkspace::~CParticleWorkspace() +{ + for (size_t i = 0; i < _Nodes.size(); i++) + delete _Nodes[i]; + _Nodes.clear(); +} + +void CParticleWorkspace::init(const std::string &filename) +{ + _Filename = filename; +} + +void CParticleWorkspace::setName(const std::string &name) +{ + _Name = name; + touch(); +} + +void CParticleWorkspace::setFileName(const std::string &fileName) +{ + _Filename = fileName; +} + +std::string CParticleWorkspace::getFilename() const +{ + return CFile::getFilename(_Filename); +} + +CWorkspaceNode *CParticleWorkspace::addNode(const std::string &filenameWithFullPath) throw( NLMISC::Exception) +{ + // Check that file is not already inserted + std::string fileName = NLMISC::CFile::getFilename(filenameWithFullPath); + for(uint k = 0; k < _Nodes.size(); ++k) + { + if (NLMISC::nlstricmp(_Nodes[k]->getFilename(), fileName) == 0) return NULL; + } +// char resultPath[MAX_PATH]; +// std::string dosPath = NLMISC::CPath::standardizeDosPath(getPath()); + std::string relativePath; +// if (!PathRelativePathTo(resultPath, dosPath.c_str(), FILE_ATTRIBUTE_DIRECTORY, filenameWithFullPath.c_str(), 0)) +// { + relativePath = filenameWithFullPath; +// } +// else +// { +// relativePath = resultPath; +// } + if (relativePath.size() >= 2) + { + if (relativePath[0] == '\\' && relativePath[1] != '\\') + { + relativePath = relativePath.substr(1); + } + } + CWorkspaceNode *newNode = new CWorkspaceNode(); + newNode->init(this); + newNode->setRelativePath(relativePath); + _Nodes.push_back(newNode); + setModifiedFlag(true); + return newNode; +} + +void CParticleWorkspace::removeNode(uint index) +{ + nlassert(index < _Nodes.size()); + _Nodes[index] = NULL; // delete the smart-ptr target + _Nodes.erase(_Nodes.begin() + index); + touch(); +} + +void CParticleWorkspace::removeNode(CWorkspaceNode *ptr) +{ + sint index = getIndexFromNode(ptr); + nlassert(index != -1); + removeNode((uint) index); +} + +void CParticleWorkspace::save() throw(NLMISC::EStream) +{ + NLMISC::COFile stream; + stream.open(_Filename); + NLMISC::COXml xmlStream; + xmlStream.init(&stream); + this->serial(xmlStream); + clearModifiedFlag(); +} + +void CParticleWorkspace::load() throw(NLMISC::EStream) +{ + NLMISC::CIFile stream; + stream.open(_Filename); + NLMISC::CIXml xmlStream; + xmlStream.init(stream); + this->serial(xmlStream); + clearModifiedFlag(); +} + +void CParticleWorkspace::serial(NLMISC::IStream &f) throw(NLMISC::EStream) +{ + f.xmlPush("PARTICLE_WORKSPACE"); + f.serialVersion(0); + f.xmlSerial(_Name, "NAME"); + f.xmlPush("PS_LIST"); + uint32 numNodes = (uint32)_Nodes.size(); + // TODO : avoid to store the number of nodes + f.xmlSerial(numNodes, "NUM_NODES"); + if (f.isReading()) + { + for(uint k = 0; k < numNodes; ++k) + { + _Nodes.push_back(new CWorkspaceNode()); + _Nodes.back()->init(this); + f.serial(*_Nodes.back()); + } + } + else + { + for(uint k = 0; k < numNodes; ++k) + { + f.serial(*_Nodes[k]); + } + } + f.xmlPop(); + f.xmlPop(); +} + +std::string CParticleWorkspace::getPath() const +{ + return NLMISC::CPath::standardizePath(NLMISC::CFile::getPath(_Filename)); +} + +sint CParticleWorkspace::getIndexFromNode(CWorkspaceNode *node) const +{ + nlassert(node); + nlassert(node->getWorkspace() == this); + for(uint k = 0; k < _Nodes.size(); ++k) + { + if (node == _Nodes[k]) return (sint) k; + } + return -1; +} + +bool CParticleWorkspace::containsFile(std::string filename) const +{ + for(uint k = 0; k < _Nodes.size(); ++k) + { + if (NLMISC::nlstricmp(filename, _Nodes[k]->getFilename()) == 0) return true; + } + return false; +} + +void CParticleWorkspace::nodeModified(CWorkspaceNode &node) +{ + nlassert(node.getWorkspace() == this); + if (_ModificationCallback) + { + _ModificationCallback->nodeModifiedFlagChanged(node); + } +} + +CWorkspaceNode *CParticleWorkspace::getNodeFromPS(NL3D::CParticleSystem *ps) const +{ + for(uint k = 0; k < _Nodes.size(); ++k) + { + if (_Nodes[k]->getPSPointer() == ps) return _Nodes[k]; + } + return NULL; +} + +void CParticleWorkspace::setModifiedFlag(bool modified) +{ + if (_Modified == modified) return; + _Modified = modified; + if (_ModificationCallback) _ModificationCallback->workspaceModifiedFlagChanged(*this); +} + +bool CParticleWorkspace::isContentModified() const +{ + for(uint k = 0; k < _Nodes.size(); ++k) + { + if (_Nodes[k]->isModified()) return true; + } + return false; +} + +void CParticleWorkspace::restickAllObjects() +{ +/* for(uint k = 0; k < _Nodes.size(); ++k) + { + std::string parentSkelName = _Nodes[k]->getParentSkelName(); + std::string parentBoneName = _Nodes[k]->getParentBoneName(); + // + _Nodes[k]->unstickPSFromSkeleton(); + if (!parentSkelName.empty()) + // find instance to stick to in the scene + for(uint l = 0; l < ov->getNumInstance(); ++l) + { + CInstanceInfo *ii = ov->getInstance(l); + if (ii->TransformShape && ii->Saved.ShapeFilename == parentSkelName) + { + NL3D::CSkeletonModel *skel = dynamic_cast(ii->TransformShape); + if (skel) + { + sint boneID = skel->getBoneIdByName(parentBoneName); + if (boneID != -1) + { + _Nodes[k]->stickPSToSkeleton(skel, (uint) boneID, parentSkelName, parentBoneName); + break; + } + } + } + } + }*/ +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_node.h b/code/nel/tools/3d/object_viewer_qt/src/particle_node.h new file mode 100644 index 000000000..f512eaeac --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_node.h @@ -0,0 +1,295 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#ifndef PARTICLE_NODE_H +#define PARTICLE_NODE_H + +// STL includes +#include +#include + +// NeL includes +#include "nel/misc/smart_ptr.h" +#include "nel/misc/stream.h" +#include +#include "nel/3d/skeleton_model.h" + +// Projects includes +#include "ps_initial_pos.h" + + +namespace NL3D { + class CParticleSystem; + class CParticleSystemModel; + class CShapeBank; +} + +namespace NLQT { + +class CParticleWorkspace; + +/** +@class CWorkspaceNode +@author Nicolas Vizerie +@author Nevrax France +@date 2004 +@brief A node in the workspace particles system editor. +@details Contains NeL system of particles and allows them to perform the following operations: +- Creating a new empty particle system (createEmptyPS()). +- Loading and saving the existing system of particles. +- Linking / unlinking of the system of particles to any bone of the skeleton model. +- Operations with the name and directory location of the particles system file. +- Has a flag indicating if the particles system changed or not (intended for the editor) +*/ +class CWorkspaceNode : public NLMISC::CRefCount +{ +public: + void init(CParticleWorkspace *ws); + + void setRelativePath(const std::string &relativePath); + + const std::string &getRelativePath() const { return _RelativePath; } + + std::string getFullPath() const; + + std::string getFilename() const { return NLMISC::CFile::getFilename(_RelativePath); } + + /// Serial node information into workspace stream. This does not save the particle system shape, only a reference to its file + void serial(NLMISC::IStream &f); + + /// Save the particle system target file + void savePS() throw(NLMISC::EStream); + + /// Save particle system with an arbitrary filename + void savePSAs(const std::string &fullPath) throw(NLMISC::EStream); + + /// put back in the unloaded state + void unload(); + + /// Load the particle system target file + /// @return true if loading succeed (false means that loading was ok, but this is not a particle system). Other cases throw an exception. + bool loadPS() throw(NLMISC::EStream); + + /// Create an empty particle system + void createEmptyPS(); + + /// Helper flag to know if a ps has been modified + /// @{ + bool isModified() const { return _Modified; } + void setModified(bool modified); + /// @} + NL3D::CParticleSystem *getPSPointer() const { return _PS; } + NL3D::CParticleSystemModel *getPSModel() const { return _PSM; } + + /// See if this node ps has been loaded + bool isLoaded() const { return _PS != NULL; } + + /// Get the workspace in which this node is inserted + CParticleWorkspace *getWorkspace() const { return _WS; } + + /// Memorize current position of object in the system. Useful to play the system because instances can be created / deleted + void memorizeState(); + + /// Restore state previously memorize. Is usually called when the user stops a particle system + void restoreState(); + + /// Test if state is currenlty memorized + bool isStateMemorized() const; + + /// For edition : If the state of the system has been memorized, keep it on par with the system when it is modified + void removeLocated(NL3D::CPSLocated *loc); + + /// For edition : If the state of the system has been memorized, keep it on par with the system when it is modified + void removeLocatedBindable(NL3D::CPSLocatedBindable *lb); + + /// Returns the skeleton to which the ps is currently sticked + NL3D::CSkeletonModel *getParentSkel() const { return _ParentSkel; } + + const std::string &getParentSkelName() const { return _ParentSkelName; } + + const std::string &getParentBoneName() const { return _ParentBoneName; } + + std::string getTriggerAnim() { return _TriggerAnim; } + + void setTriggerAnim(const std::string &anim); + +private: + std::string _TriggerAnim; + NL3D::CParticleSystem *_PS; + NL3D::CParticleSystemModel *_PSM; + + // Keep a shape bank per node because we want the whole path to identify the ps, not just its filename + // (shape bank keeps the filename only) + NL3D::CShapeBank *_ShapeBank; + + // Relative path from which the ps was inserted + // relative path is also a unique identifier for this ps in the workspace + std::string _RelativePath; + + // initial pos of system. Allow to restore the initial instances of the system when doing start / stop + CPSInitialPos _InitialPos; + bool _Modified; + CParticleWorkspace *_WS; + NLMISC::CRefPtr _ParentSkel; + bool _ResetAutoCount; + // + std::string _ParentSkelName; + std::string _ParentBoneName; +private: + void setup(NL3D::CParticleSystemModel &psm); +public: + bool getResetAutoCountFlag() const { return _ResetAutoCount; } + void setResetAutoCountFlag(bool reset) { _ResetAutoCount = reset; } + + /// Stick to a skeleton + void stickPSToSkeleton(NL3D::CSkeletonModel *skel, + uint bone, + const std::string &parentSkelName, // for callback after loading + const std::string &parentBoneName + ); + void unstickPSFromSkeleton(); +private: + friend class CParticleWorkspace; + // Ctor + CWorkspaceNode(); +public: + // DTor + ~CWorkspaceNode(); +}; /* class CWorkspaceNode */ + +typedef std::vector > TNodeVect; +typedef TNodeVect::iterator TPWNodeItr; + +/** +@class CParticleWorkspace +@author Nicolas Vizerie +@author Nevrax France +@date 2004 +@brief A container containing all the loaded particle system. +@details Allows you to load \ save in xml file list of systems of particles (the path to each file as well) +and additional parameters (skeleton model, and to which bone it is attached) +There is a feedback mechanism showed changes are being made with the container,with a particle system or connection to the skeleton. +*/ +class CParticleWorkspace +{ +public: + /// callback to know when a workspace node has been modified + struct IModificationCallback + { + virtual void workspaceModifiedFlagChanged(CParticleWorkspace &pw) = 0; + virtual void nodeModifiedFlagChanged(CWorkspaceNode &node) = 0; + virtual void nodeSkelParentChanged(CWorkspaceNode &node) = 0; // called when fx has been linked / unlinked from a skeleton parent + }; + /// Сonstructor + CParticleWorkspace(); + /// Destructor + ~CParticleWorkspace(); + + /// Init the workspace for the given object viewer + /// must be called prior to other methods + void init(const std::string &filename); + + /// Set a new name for the workspace (not its filename) + void setName(const std::string &name); + + /// Set a new file name for the workspace + void setFileName(const std::string &fileName); + + std::string getName() const { return _Name; } + + /// Get the path in which workpsace is located with a trailing slash + std::string getPath() const; + std::string getFilename() const; + + /// Get Number of nodes in the workspace + uint getNumNode() const { return (uint)_Nodes.size(); } + + /// Get a node in workspace + /// Can keep pointer safely as long as the node is not deleted + CWorkspaceNode *getNode(uint index) const { return _Nodes[index]; } + + /// Get a node from a pointer on a particle system + CWorkspaceNode *getNodeFromPS(NL3D::CParticleSystem *ps) const; + + /// Test if the workspace already contains a node with the given filename name + /// NB : 2 node with the same name re not allowed, even if their path is different + bool containsFile(std::string filename) const; + + /// Add a node in the workspace. Will succeed only if fx filename does not already exist in the workspace. + /// The node is in the 'unloaded' state, so caller must load it afterward. + /// NB : no lookup is done, full path must be provided. + /// @return pointer to new node, or NULL if already inserted + CWorkspaceNode *addNode(const std::string &filenameWithFullPath) throw( NLMISC::Exception); + + /// Remove a node by it's index + void removeNode(uint index); + + /// Remove a node by it's pointer + void removeNode(CWorkspaceNode *ptr); + + /// Get index of a node from its pointer, or -1 if not found + sint getIndexFromNode(CWorkspaceNode *node) const; + + /// Save the workspace structure. The target file is the one given when this object was created + /// NB : ps shape are not saved, only the structure is. To save the shapes, call CNode::save() + void save() throw(NLMISC::EStream); + + /// Load the workspace structure. The target file is the one given when this object was created + /// All nodes are in the 'unloaded" state, so it is to the caller to load them by calling load() on their node + void load() throw(NLMISC::EStream); + + /// Test wether the structure of the workspace has been modified (does not test if ps inside the workspace have been modified) + bool isModified() const { return _Modified; } + + /// Test wether the content of the workspace has ben modified + bool isContentModified() const; + void touch() { setModifiedFlag(true); } + void clearModifiedFlag() { setModifiedFlag(false); } + + /// Set a callback to know when a node is modified + void setModificationCallback(IModificationCallback *cb) { _ModificationCallback = cb; } + IModificationCallback *getModificationCallback() const { return _ModificationCallback; } + + /// Restick all objects, useful after loading + void restickAllObjects(); + + TNodeVect& getNodeList() { return _Nodes; } + +private: + // use smart ptr to avoir prb wih resize + TNodeVect _Nodes; + // path + name of workspace + std::string _Filename; + bool _Modified; + IModificationCallback *_ModificationCallback; + // workspace user name + std::string _Name; + + /// serial the object + void serial(NLMISC::IStream &f) throw(NLMISC::EStream); + + /// set the 'modified flag' and call the callback + void setModifiedFlag(bool modified); + +public: + void nodeModified(CWorkspaceNode &node); +}; /* class CParticleWorkspace */ + + +} /* namespace NLQT */ + +#endif // PARTICLE_NODE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.cpp new file mode 100644 index 000000000..1c54ec21e --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.cpp @@ -0,0 +1,139 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_property_dialog.h" + +// Qt includes + +// Project includes +#include "modules.h" + +namespace NLQT { + +CPropertyDialog::CPropertyDialog(CParticleTreeModel *treeModel, QWidget *parent) + : QDockWidget(parent) +{ + _treeModel = treeModel; + + setupUi(); +} +CPropertyDialog::~CPropertyDialog() +{ +} + +void CPropertyDialog::setupUi() +{ + setObjectName(QString::fromUtf8("CPropertyDialog")); + + QIcon icon; + icon.addFile(QString::fromUtf8(":/images/pqrticles.png"), QSize(), QIcon::Normal, QIcon::Off); + setWindowIcon(icon); + + _dockWidgetContents = new QWidget(); + _gridLayout = new QGridLayout(_dockWidgetContents); + _scrollArea = new QScrollArea(_dockWidgetContents); + _scrollArea->setWidgetResizable(true); + _scrollAreaWidgetContents = new QWidget(); + + _pagesGridLayout = new QGridLayout(_scrollAreaWidgetContents); + + _stackedWidget = new QStackedWidget(_scrollAreaWidgetContents); + + _wpPage = new CWorkspacePage(_treeModel); + _stackedWidget->addWidget(_wpPage); + _psPage = new CParticleSystemPage(_stackedWidget); + _stackedWidget->addWidget(_psPage); + _locatedBindablePage = new CLocatedBindablePage(_stackedWidget); + _stackedWidget->addWidget(_locatedBindablePage); + _locatedPage = new CLocatedPage(_stackedWidget); + _stackedWidget->addWidget(_locatedPage); + _forcePage = new CForcePage(_stackedWidget); + _stackedWidget->addWidget(_forcePage); + _lightPage = new CLightPage(_stackedWidget); + _stackedWidget->addWidget(_lightPage); + _zonePage = new CZonePage(_stackedWidget); + _stackedWidget->addWidget(_zonePage); + _soundPage = new CSoundPage(_stackedWidget); + _stackedWidget->addWidget(_soundPage); + _emitterPage = new CEmitterPage(_stackedWidget); + _stackedWidget->addWidget(_emitterPage); + _psMoverPage = new CPSMoverPage(_stackedWidget); + _stackedWidget->addWidget(_psMoverPage); + + _pagesGridLayout->addWidget(_stackedWidget, 0, 0, 1, 1); + + _scrollArea->setWidget(_scrollAreaWidgetContents); + + _gridLayout->addWidget(_scrollArea, 0, 0, 1, 1); + + setWidget(_dockWidgetContents); + setWindowTitle(tr("Property editor")); +} + +void CPropertyDialog::setCurrentEditedElement(CParticleTreeItem *editedItem) +{ + // Update ui property editor + switch (editedItem->itemType()) + { + case ItemType::ParticleSystem: + _psPage->setEditedParticleSystem(editedItem->getNode()); + _stackedWidget->setCurrentWidget(_psPage); + break; + case ItemType::Particle: + _locatedBindablePage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getBind()); + _stackedWidget->setCurrentWidget(_locatedBindablePage); + break; + case ItemType::Emitter: + _emitterPage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getBind()); + _stackedWidget->setCurrentWidget(_emitterPage); + break; + case ItemType::Force: + _forcePage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getBind()); + _stackedWidget->setCurrentWidget(_forcePage); + break; + case ItemType::Light: + _lightPage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getBind()); + _stackedWidget->setCurrentWidget(_lightPage); + break; + case ItemType::Sound: + _soundPage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getBind()); + _stackedWidget->setCurrentWidget(_soundPage); + break; + case ItemType::Located: + _locatedPage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getLoc()); + _stackedWidget->setCurrentWidget(_locatedPage); + break; + case ItemType::CollisionZone: + _zonePage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getBind()); + _stackedWidget->setCurrentWidget(_zonePage); + break; + case ItemType::LocatedInstance: + _psMoverPage->setEditedItem(_treeModel->getOwnerNode(editedItem) ,editedItem->getLoc(), editedItem->getLocatedInstanceIndex()); + _stackedWidget->setCurrentWidget(_psMoverPage); + _treeModel->getOwnerNode(editedItem)->getPSPointer()->setCurrentEditedElement(editedItem->getLoc(), + editedItem->getLocatedInstanceIndex(), + _psMoverPage->getLocatedBindable()); + break; + default: + _stackedWidget->setCurrentWidget(_wpPage); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.h new file mode 100644 index 000000000..a3b8b8f0b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_property_dialog.h @@ -0,0 +1,89 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_PROPERTY_DIALOG_H +#define PARTICLE_PROPERTY_DIALOG_H + +// Qt includes +#include +#include +#include +#include +#include + +// NeL includes +#include "nel/misc/smart_ptr.h" + +// Projects includes +#include "particle_system_page.h" +#include "emitter_page.h" +#include "located_page.h" +#include "located_bindable_page.h" +#include "particle_force_page.h" +#include "particle_light_page.h" +#include "particle_zone_page.h" +#include "particle_sound_page.h" +#include "particle_workspace_page.h" +#include "ps_mover_page.h" +#include "particle_tree_model.h" +#include "particle_node.h" + +namespace NLQT { + +class CPropertyDialog: public QDockWidget +{ + Q_OBJECT +public: + CPropertyDialog(CParticleTreeModel *treeModel, QWidget *parent = 0); + ~CPropertyDialog(); + + void setCurrentEditedElement(CParticleTreeItem *editedItem); + + CLocatedPage *getLocatedPage() const { return _locatedPage; }; + CPSMoverPage *getMoverPage() const { return _psMoverPage; }; + +private: + void setupUi(); + + QWidget *_dockWidgetContents; + QGridLayout *_gridLayout; + QGridLayout *_pagesGridLayout; + QScrollArea *_scrollArea; + QWidget *_scrollAreaWidgetContents; + + QStackedWidget *_stackedWidget; + CWorkspacePage *_wpPage; + CParticleSystemPage *_psPage; + CLocatedBindablePage *_locatedBindablePage; + CLocatedPage *_locatedPage; + CForcePage *_forcePage; + CLightPage *_lightPage; + CZonePage *_zonePage; + CSoundPage *_soundPage; + CEmitterPage *_emitterPage; + CPSMoverPage *_psMoverPage; + + CParticleTreeModel *_treeModel; + + friend class CParticleWorkspaceDialog; +}; /* class CPropertyDialog */ + +} /* namespace NLQT */ + +#endif // PARTICLE_PROPERTY_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_sound_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_sound_form.ui new file mode 100644 index 000000000..783126540 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_sound_form.ui @@ -0,0 +1,189 @@ + + + CSoundPage + + + + 0 + 0 + 314 + 334 + + + + Form + + + + + + QTabWidget::East + + + 0 + + + + General + + + + + + Sound name + + + + + + true + + + + + + + Browse + + + + + + + Qt::Horizontal + + + + 93 + 20 + + + + + + + + Play + + + + + + + + + + + + Spawn + + + + + + + Mute + + + + + + + + + Keep original pitch + + + + + + + Emission percent: + + + + + + + + + + Qt::Vertical + + + + 20 + 51 + + + + + + + + + Sound volume + + + + + + + + + Qt::Vertical + + + + 20 + 209 + + + + + + + + + Sound pitch + + + + + + + + + Qt::Vertical + + + + 20 + 208 + + + + + + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CAttribFloatWidget + QGroupBox +
attrib_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.cpp new file mode 100644 index 000000000..daa9896e7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.cpp @@ -0,0 +1,168 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_sound_page.h" + +// Qt includes +#include + +// NeL includes +#include "nel/3d/ps_located.h" +#include "nel/3d/u_particle_system_sound.h" +#include "nel/3d/particle_system.h" + +// Project includes +#include "modules.h" +#include "sound_system.h" + +namespace NLQT { + +CSoundPage::CSoundPage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + // setup dialog the sounds gain + _ui.gainWidget->setRange(0.f, 1.f); + _ui.gainWidget->setWrapper(&_GainWrapper); + _ui.gainWidget->setSchemeWrapper(&_GainWrapper); + _ui.gainWidget->init(); + + // setup dialog the sounds pitch + _ui.pitchWidget->setRange(0.001f, 5.f); + _ui.pitchWidget->setWrapper(&_PitchWrapper); + _ui.pitchWidget->setSchemeWrapper(&_PitchWrapper); + _ui.pitchWidget->init(); + + // setup dialog the percent of sound emissions + _ui.emissionWidget->setRange(0.f, 1.f); + _ui.emissionWidget->setWrapper(&_EmissionPercentWrapper); + + connect(_ui.browsePushButton ,SIGNAL(clicked()), this, SLOT(browse())); + connect(_ui.playPushButton ,SIGNAL(clicked()), this, SLOT(play())); + connect(_ui.spawnCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setSpawn(bool))); + connect(_ui.muteCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setMute(bool))); + connect(_ui.keepPitchCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setKeepPitch(bool))); + connect(_ui.soundNameLineEdit ,SIGNAL(textChanged(QString)), this, SLOT(setSoundName(QString))); +} + +CSoundPage::~CSoundPage() +{ +} + +void CSoundPage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable) +{ + _Sound = static_cast(locatedBindable); + _Node = ownerNode; + + nlassert(_Sound); + + _EmissionPercentWrapper.OwnerNode = _Node; + _EmissionPercentWrapper.S = _Sound; + _ui.emissionWidget->updateUi(); + + _GainWrapper.S = _Sound; + _ui.gainWidget->setWorkspaceNode(_Node); + _ui.gainWidget->updateUi(); + + _PitchWrapper.S = _Sound; + _ui.pitchWidget->setWorkspaceNode(_Node); + _ui.pitchWidget->updateUi(); + + _ui.soundNameLineEdit->setText(QString(NLMISC::CStringMapper::unmap(_Sound->getSoundName()).c_str())); + + _ui.spawnCheckBox->setChecked(_Sound->getSpawn()); + _ui.muteCheckBox->setChecked(_Sound->getMute()); + _ui.keepPitchCheckBox->setChecked(_Sound->getUseOriginalPitchFlag()); +} + +void CSoundPage::browse() +{ + std::vector names; + + + NLSOUND::UAudioMixer *audioMixer = Modules::sound().getAudioMixer(); + if (audioMixer) + { + audioMixer->getSoundNames(names); + } + + // TODO: create CPickSound dialog + QStringList items; + items << tr(""); + for(size_t i = 0; i < names.size(); ++i) + items << QString(names[i]->c_str()); + + bool ok; + QString item = QInputDialog::getItem(this, tr("Select your sound"), + tr("Sound:"), items, 0, false, &ok); + if (ok) + { + _ui.soundNameLineEdit->setText(item); + updateModifiedFlag(); + } +} + +void CSoundPage::play() +{ + Modules::sound().play(_ui.soundNameLineEdit->text().toStdString()); +} + +void CSoundPage::setSpawn(bool state) +{ + if (state != _Sound->getSpawn()) + { + _Sound->setSpawn(state); + updateModifiedFlag(); + } +} + +void CSoundPage::setMute(bool state) +{ + if (state != _Sound->getMute()) + { + _Sound->setMute(state); + updateModifiedFlag(); + } +} + +void CSoundPage::setKeepPitch(bool state) +{ + bool hadScheme = _PitchWrapper.getScheme() != NULL; + if (state != _Sound->getUseOriginalPitchFlag()) + { + _Sound->setUseOriginalPitchFlag(state); + updateModifiedFlag(); + } + if (state) + { + if (hadScheme) _ui.pitchWidget->updateUi(); + ///!!!!! + ///_PitchDlg->closeEditWindow(); + } + _ui.pitchWidget->setEnabled(!state); +} + +void CSoundPage::setSoundName(const QString &text) +{ + _Sound->setSoundName(NLMISC::CStringMapper::map(text.toStdString())); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.h b/code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.h new file mode 100644 index 000000000..825bdb7c6 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_sound_page.h @@ -0,0 +1,109 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_SOUND_PAGE_H +#define PARTICLE_SOUND_PAGE_H + +#include +#include "ui_particle_sound_form.h" + +// STL includes + +// NeL includes +#include "nel/3d/ps_sound.h" + +// Project includes + +#include "ps_wrapper.h" +#include "particle_node.h" + +namespace NLSOUND +{ + class UAudioMixer; +} + +namespace NLQT { + +/** +@class CSoundPage +@brief Page for QStackWidget, to edit sounds in a particle system +*/ +class CSoundPage: public QWidget +{ + Q_OBJECT + +public: + CSoundPage(QWidget *parent = 0); + ~CSoundPage(); + + /// Set the sounds to edit. + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable); + +private Q_SLOTS: + void browse(); + void play(); + void setSpawn(bool state); + void setMute(bool state); + void setKeepPitch(bool state); + void setSoundName(const QString &text); + +private: + + /// wrapper to set the gain of sounds + struct CGainWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSSound *S; + float get(void) const { return S->getGain(); } + void set(const float &v) { S->setGain(v); } + scheme_type *getScheme(void) const { return S->getGainScheme(); } + void setScheme(scheme_type *s) { S->setGainScheme(s); } + } _GainWrapper; + + /// wrapper to set the pitch of sounds + struct CPitchWrapper : public IPSWrapperFloat, IPSSchemeWrapperFloat + { + NL3D::CPSSound *S; + float get(void) const { return S->getPitch(); } + void set(const float &v) { S->setPitch(v); } + scheme_type *getScheme(void) const { return S->getPitchScheme(); } + void setScheme(scheme_type *s) { S->setPitchScheme(s); } + } _PitchWrapper; + + /// wrapper to set the percentage of sound emissions + struct CEmissionPercentWrapper : public IPSWrapperFloat + { + NL3D::CPSSound *S; + float get(void) const { return S->getEmissionPercent(); } + void set(const float &v) { S->setEmissionPercent(v); } + } _EmissionPercentWrapper; + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + /// the sound being edited + NL3D::CPSSound *_Sound; + + CWorkspaceNode *_Node; + + Ui::CSoundPage _ui; + +}; /* class CSoundPage */ + +} /* namespace NLQT */ + +#endif // PARTICLE_SOUND_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_system_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_system_form.ui new file mode 100644 index 000000000..7a2a095f0 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_system_form.ui @@ -0,0 +1,909 @@ + + + CParticleSystemPage + + + + 0 + 0 + 334 + 495 + + + + Form + + + + + + QTabWidget::East + + + 0 + + + true + + + + Integration + + + + + + + 0 + 0 + + + + Force global lighting + + + + + + + + 0 + 0 + + + + Enable load balancing + + + + + + + + 0 + 0 + + + + Accurate integration + + + + + + + Enable motion slowDown + + + + + + + Time threshold: + + + + + + + + + + Max steps: + + + + + + + + + + + 0 + 0 + + + + Lock + + + true + + + + + + + Qt::Vertical + + + + 20 + 112 + + + + + + + + + User param + + + + + + + + User param1: + + + + + + + Qt::Horizontal + + + + 146 + 20 + + + + + + + + + 0 + 0 + + + + Global + + + + + + + + + + + + + + User param2: + + + + + + + Qt::Horizontal + + + + 146 + 20 + + + + + + + + + 0 + 0 + + + + Global + + + + + + + + + + + + + + User param3: + + + + + + + Qt::Horizontal + + + + 146 + 20 + + + + + + + + Global + + + + + + + + + + + + + + User param4: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Global + + + + + + + + + + + + Qt::Vertical + + + + 20 + 131 + + + + + + + + + PBBox / LOD param + + + + + + + 0 + 0 + + + + Enable Precomputed Bounding Box + + + + + + + false + + + + + + + + + Auto + + + + + + + Qt::Horizontal + + + + 93 + 20 + + + + + + + + Reset + + + + + + + + + X: + + + + + + + + 0 + 0 + + + + false + + + QAbstractSpinBox::NoButtons + + + 999.000000000000000 + + + + + + + Y: + + + + + + + + 0 + 0 + + + + true + + + QAbstractSpinBox::NoButtons + + + 999.000000000000000 + + + + + + + + 0 + 0 + + + + Z: + + + + + + + + 0 + 0 + + + + QAbstractSpinBox::NoButtons + + + 999.000000000000000 + + + + + + + + + + 0 + 0 + + + + + 40 + 23 + + + + + 40 + 23 + + + + +10 % + + + + + + + + 0 + 0 + + + + + 40 + 23 + + + + + 40 + 23 + + + + -10 % + + + + + + + Qt::Horizontal + + + + 107 + 20 + + + + + + + + + + + Qt::Horizontal + + + + + + + Max view dist: + + + + + + + + + + LOD Ratio: + + + + + + + + + + Qt::Vertical + + + + 20 + 132 + + + + + + + + + Global color + + + + + + + + + + + + + + + Edit global color + + + + + + + true + + + + + + + + + + Wrn: Bypass multitex, not supported by all types + + + + + + + Qt::Vertical + + + + 20 + 237 + + + + + + + + + Life mgt param + + + + + + Life mgt presets: + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + Environment FX + + + + + Running Environment FX + + + + + Spell FX + + + + + Looping Spell FX + + + + + Minor transitory FX + + + + + User defined + + + + + Moving Looping FX + + + + + Spawned Environment FX + + + + + Ground FX + + + + + Projectile FX + + + + + + + + + + Sharable + + + + + + + Auto-LOD + + + + + + + false + + + + 0 + 0 + + + + + 53454 + 16777215 + + + + Settings + + + + + + + + + + + + + + + Model removed when out of range + + + + + + + PS resource removed when not visible + + + + + + + Force life time update + + + + + + + No max nb steps + + + + + + + + + Anim type: + + + + + + + + 0 + 0 + + + + Die on event: + + + + + + + + + + + + 0 + 0 + + + + + When visible + + + + + When in visible clusters + + + + + Always + + + + + + + + + 0 + 0 + + + + + Disabled + + + + + No more particles + + + + + No more particles/emitters + + + + + + + + + + + + + 0 + 0 + + + + Apply after delay: + + + + + + + + 0 + 0 + + + + s + + + + + + + + + Auto delay + + + + + + + + + + Qt::Vertical + + + + 20 + 13 + + + + + + + + + + + + + NLQT::CEditRangeUIntWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CAttribRGBAWidget + QGroupBox +
attrib_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_system_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_system_page.cpp new file mode 100644 index 000000000..2b806fdfb --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_system_page.cpp @@ -0,0 +1,667 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_system_page.h" + +// Qt includes +#include +#include +#include +#include + +// NeL includes +#include +#include +#include + +// Project includes +#include "modules.h" +#include "auto_lod_dialog.h" + +using namespace NL3D; +using namespace NLMISC; + +namespace NLQT { + +// WRAPPERS IMPLEMENTATION + +float CTimeThresholdWrapper::get(void) const +{ + NL3D::TAnimationTime t; + uint32 max; + bool csd; + bool klt; + PS->getAccurateIntegrationParams(t, max, csd, klt); + return t; +} + +void CTimeThresholdWrapper::set(const float &tt) +{ + NL3D::TAnimationTime t; + uint32 max; + bool csd; + bool klt; + PS->getAccurateIntegrationParams(t, max, csd, klt); + PS->setAccurateIntegrationParams(tt, max, csd, klt); +} + +uint32 CMaxNbIntegrationWrapper::get(void) const +{ + NL3D::TAnimationTime t; + uint32 max; + bool csd; + bool klt; + PS->getAccurateIntegrationParams(t, max, csd, klt); + return max; +} + +void CMaxNbIntegrationWrapper::set(const uint32 &nmax) +{ + NL3D::TAnimationTime t; + uint32 max; + bool csd; + bool klt; + PS->getAccurateIntegrationParams(t, max, csd, klt); + PS->setAccurateIntegrationParams(t, nmax, csd, klt); +} + +float CUserParamWrapper::get(void) const +{ + return PS->getUserParam(Index); +} + +void CUserParamWrapper::set(const float &v) +{ + PS->setUserParam(Index, v); +} + +float CMaxViewDistWrapper::get(void) const +{ + return PS->getMaxViewDist(); +} + +void CMaxViewDistWrapper::set(const float &d) +{ + PS->setMaxViewDist(d); +} + +float CLODRatioWrapper::get(void) const +{ + return PS->getLODRatio(); +} + +void CLODRatioWrapper::set(const float &v) +{ + PS->setLODRatio(v); +} + +static void chooseGlobalUserParam(uint userParam, NL3D::CParticleSystem *ps, QWidget *parent) +{ + nlassert(ps); + bool ok; + QString text = QInputDialog::getText(parent, "Choose Global User Param", + "User name:", QLineEdit::Normal, + QString(ps->getGlobalValueName(userParam).c_str()), &ok); + + if (ok) + ps->bindGlobalValueToUserParam(text.toStdString(), userParam); +} + +CParticleSystemPage::CParticleSystemPage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.timeThresholdWidget->setRange(0.005f, 0.3f); + _ui.timeThresholdWidget->enableLowerBound(0, true); + _ui.timeThresholdWidget->setWrapper(&_TimeThresholdWrapper); + + _ui.maxStepsWidget->setRange(0, 4); + _ui.maxStepsWidget->enableLowerBound(0, true); + _ui.maxStepsWidget->setWrapper(&_MaxNbIntegrationWrapper); + + _ui.userParamWidget_1->setRange(0, 1.0f); + _ui.userParamWidget_1->enableLowerBound(0, false); + _ui.userParamWidget_1->enableUpperBound(1, false); + _ui.userParamWidget_1->setWrapper(&_UserParamWrapper[0]); + + _ui.userParamWidget_2->setRange(0, 1.0f); + _ui.userParamWidget_2->enableLowerBound(0, false); + _ui.userParamWidget_2->enableUpperBound(1, false); + _ui.userParamWidget_2->setWrapper(&_UserParamWrapper[1]); + + _ui.userParamWidget_3->setRange(0, 1.0f); + _ui.userParamWidget_3->enableLowerBound(0, false); + _ui.userParamWidget_3->enableUpperBound(1, false); + _ui.userParamWidget_3->setWrapper(&_UserParamWrapper[2]); + + _ui.userParamWidget_4->setRange(0, 1.0f); + _ui.userParamWidget_4->enableLowerBound(0, false); + _ui.userParamWidget_4->enableUpperBound(1, false); + _ui.userParamWidget_4->setWrapper(&_UserParamWrapper[3]); + + _ui.maxViewDistWidget->setRange(0, 400.f); + _ui.maxViewDistWidget->enableLowerBound(0, true); + _ui.maxViewDistWidget->setWrapper(&_MaxViewDistWrapper); + + _ui.lodRatioWidget->setRange(0, 1.f); + _ui.lodRatioWidget->enableLowerBound(0, true); + _ui.lodRatioWidget->enableUpperBound(1, true); + _ui.lodRatioWidget->setWrapper(&_LODRatioWrapper); + + _ui.colorWidget->setSchemeWrapper(&_GlobalColorWrapper); + _ui.colorWidget->enableMemoryScheme(false); + _ui.colorWidget->enableNbCycles(false); + _ui.colorWidget->enableSrcInput(false); + _ui.colorWidget->setEnabledConstantValue(false); + _ui.colorWidget->init(); + _ui.colorWidget->hide(); + + for (uint k = 0; k < NL3D::MaxPSUserParam; ++k) + _UserParamWrapper[k].Index = k; + + connect(_ui.globalLightCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setGlobalLight(bool))); + connect(_ui.loadBalancingCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setLoadBalancing(bool))); + connect(_ui.integrationCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setIntegration(bool))); + connect(_ui.motionSlowDownCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setMotionSlowDown(bool))); + connect(_ui.lockPushButton ,SIGNAL(toggled(bool)), this, SLOT(setLock(bool))); + connect(_ui.globalPushButton_1 ,SIGNAL(clicked()), this, SLOT(setGloabal1())); + connect(_ui.globalPushButton_2 ,SIGNAL(clicked()), this, SLOT(setGloabal2())); + connect(_ui.globalPushButton_3 ,SIGNAL(clicked()), this, SLOT(setGloabal3())); + connect(_ui.globalPushButton_4 ,SIGNAL(clicked()), this, SLOT(setGloabal4())); + connect(_ui.enablePBBCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setEnableBbox(bool))); + connect(_ui.autoCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setAutoBbox(bool))); + connect(_ui.resetPushButton ,SIGNAL(clicked()), this, SLOT(resetBbox())); + connect(_ui.incBboxPushButton ,SIGNAL(clicked()), this, SLOT(incBbox())); + connect(_ui.decBboxPushButton ,SIGNAL(clicked()), this, SLOT(decBbox())); + connect(_ui.xDoubleSpinBox ,SIGNAL(valueChanged(double)), this, SLOT(setXBbox(double))); + connect(_ui.yDoubleSpinBox ,SIGNAL(valueChanged(double)), this, SLOT(setYBbox(double))); + connect(_ui.zDoubleSpinBox ,SIGNAL(valueChanged(double)), this, SLOT(setZBbox(double))); + connect(_ui.editGlobalColorCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setEditGlobalColor(bool))); + connect(_ui.presetBehaviourComboBox ,SIGNAL(currentIndexChanged(int)), this, SLOT(setPresetBehaviour(int))); + connect(_ui.sharableCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setSharable(bool))); + connect(_ui.autoLODCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setAutoLOD(bool))); + connect(_ui.settingsPushButton ,SIGNAL(clicked()), this, SLOT(settings())); + connect(_ui.modelRemovedCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setModelRemoved(bool))); + connect(_ui.psResourceCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setPSResource(bool))); + connect(_ui.lifeTimeUpdateCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setLifeTimeUpdate(bool))); + connect(_ui.noMaxNBStepsCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setNoMaxNBSteps(bool))); + connect(_ui.autoDelayCheckBox ,SIGNAL(toggled(bool)), this, SLOT(setAutoDelay(bool))); + connect(_ui.animTypeComboBox ,SIGNAL(currentIndexChanged(int)), this, SLOT(setAnimType(int))); + connect(_ui.dieComboBox ,SIGNAL(currentIndexChanged(int)), this, SLOT(setDie(int))); + connect(_ui.afterDelaySpinBox ,SIGNAL(valueChanged(double)), this, SLOT(setAfterDelay(double))); +} + +CParticleSystemPage::~CParticleSystemPage() +{ +} + +void CParticleSystemPage::setEditedParticleSystem(CWorkspaceNode *node) +{ + _Node = node; + // load settings Time threshold. + _TimeThresholdWrapper.OwnerNode = _Node; + _TimeThresholdWrapper.PS = _Node->getPSPointer(); + _ui.timeThresholdWidget->updateUi(); + + // load settings Max steps. + _MaxNbIntegrationWrapper.OwnerNode = _Node; + _MaxNbIntegrationWrapper.PS = _Node->getPSPointer(); + _ui.maxStepsWidget->updateUi(); + + // load settings User Param + for (uint k = 0; k < NL3D::MaxPSUserParam; ++k) + { + _UserParamWrapper[k].OwnerNode = _Node; + _UserParamWrapper[k].PS = _Node->getPSPointer(); + } + + _ui.userParamWidget_1->updateUi(); + _ui.userParamWidget_2->updateUi(); + _ui.userParamWidget_3->updateUi(); + _ui.userParamWidget_4->updateUi(); + + // load settings Max view dist. + _MaxViewDistWrapper.OwnerNode = _Node; + _MaxViewDistWrapper.PS = _Node->getPSPointer(); + _ui.maxViewDistWidget->updateUi(); + + // load settings LOD Ratio. + _LODRatioWrapper.OwnerNode = _Node; + _LODRatioWrapper.PS = _Node->getPSPointer(); + _ui.lodRatioWidget->updateUi(); + + // Integration + _ui.integrationCheckBox->setChecked(_Node->getPSPointer()->isAccurateIntegrationEnabled()); + _ui.loadBalancingCheckBox->setChecked(_Node->getPSPointer()->isLoadBalancingEnabled()); + _ui.globalLightCheckBox->setChecked(_Node->getPSPointer()->getForceGlobalColorLightingFlag()); + + NL3D::TAnimationTime t; + uint32 max; + bool csd; + bool klt; + _Node->getPSPointer()->getAccurateIntegrationParams(t, max, csd, klt); + _ui.motionSlowDownCheckBox->setChecked(csd); + + // Precomputed Bbox + _ui.enablePBBCheckBox->setChecked(!_Node->getPSPointer()->getAutoComputeBBox()); + + // global color + _GlobalColorWrapper.PS = _Node->getPSPointer(); + _ui.colorWidget->setWorkspaceNode(_Node); + int bGlobalColor = _Node->getPSPointer()->getColorAttenuationScheme() != NULL ? 1 : 0; + _ui.editGlobalColorCheckBox->setChecked(bGlobalColor); + if (bGlobalColor) + _ui.colorWidget->updateUi(); + + // Life mgt parameters + _ui.presetBehaviourComboBox->setCurrentIndex(_Node->getPSPointer()->getBehaviourType()); + _ui.sharableCheckBox->setChecked(_Node->getPSPointer()->isSharingEnabled()); + _ui.autoLODCheckBox->setChecked(_Node->getPSPointer()->isAutoLODEnabled()); + + _ui.modelRemovedCheckBox->setChecked(_Node->getPSPointer()->getDestroyModelWhenOutOfRange()); + _ui.psResourceCheckBox->setChecked(_Node->getPSPointer()->doesDestroyWhenOutOfFrustum()); + _ui.noMaxNBStepsCheckBox->setChecked(_Node->getPSPointer()->getBypassMaxNumIntegrationSteps()); + + _ui.lifeTimeUpdateCheckBox->setChecked(klt); + _ui.dieComboBox->setCurrentIndex(_Node->getPSPointer()->getDestroyCondition()); + _ui.animTypeComboBox->setCurrentIndex(_Node->getPSPointer()->getAnimType()); + _ui.autoDelayCheckBox->setChecked(_Node->getPSPointer()->getAutoComputeDelayBeforeDeathConditionTest()); +} + +void CParticleSystemPage::updatePrecomputedBBoxParams() +{ + NLMISC::CAABBox b; + _Node->getPSPointer()->computeBBox(b); + _ui.xDoubleSpinBox->setValue(b.getHalfSize().x); + _ui.yDoubleSpinBox->setValue(b.getHalfSize().y); + _ui.zDoubleSpinBox->setValue(b.getHalfSize().z); +} + +void CParticleSystemPage::updateDieOnEventParams() +{ + bool ew = _Node->getPSPointer()->getDestroyCondition() == NL3D::CParticleSystem::none ? false : true; + _ui.autoDelayCheckBox->setEnabled(ew); + bool autoDelay = _Node->getPSPointer()->getAutoComputeDelayBeforeDeathConditionTest(); + if (autoDelay) + ew = false; + _ui.afterDelaySpinBox->setEnabled(ew); + _ui.afterDelaySpinBox->setValue(_Node->getPSPointer()->getDelayBeforeDeathConditionTest()); +} + +void CParticleSystemPage::updateLifeMgtPresets() +{ + bool bEnable = _Node->getPSPointer()->getBehaviourType() == NL3D::CParticleSystem::UserBehaviour ? true : false; + + _ui.modelRemovedCheckBox->setEnabled(bEnable); + _ui.psResourceCheckBox->setEnabled(bEnable); + _ui.lifeTimeUpdateCheckBox->setEnabled(bEnable); + _ui.noMaxNBStepsCheckBox->setEnabled(bEnable); + _ui.animTypeComboBox->setEnabled(bEnable); + _ui.dieComboBox->setEnabled(bEnable); + updateDieOnEventParams(); +} + +void CParticleSystemPage::setGlobalLight(bool state) +{ + if (state == _Node->getPSPointer()->getForceGlobalColorLightingFlag()) return; + _Node->getPSPointer()->setForceGlobalColorLightingFlag(state); + if (_Node && _Node->getPSModel()) + { + _Node->getPSModel()->touchLightableState(); + } + updateModifiedFlag(); +} + +void CParticleSystemPage::setLoadBalancing(bool state) +{ + if (state == _Node->getPSPointer()->isLoadBalancingEnabled()) return; + if (state == false) + { + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, tr("Are you sure?"), + tr("Load balancing on/off"), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) + _Node->getPSPointer()->enableLoadBalancing(false); + else + _ui.loadBalancingCheckBox->setChecked(true); + } + else + { + _Node->getPSPointer()->enableLoadBalancing(true); + } + updateModifiedFlag(); +} + +void CParticleSystemPage::setIntegration(bool state) +{ + // enable/disable accurate integration. + if (state != _Node->getPSPointer()->isAccurateIntegrationEnabled()) + { + _Node->getPSPointer()->enableAccurateIntegration(state); + updateModifiedFlag(); + } + _ui.timeThresholdWidget->setEnabled(state); + _ui.maxStepsWidget->setEnabled(state); + _ui.motionSlowDownCheckBox->setEnabled(state); +} + +void CParticleSystemPage::setMotionSlowDown(bool state) +{ + NL3D::TAnimationTime t; + uint32 max; + bool csd; + bool klt; + _Node->getPSPointer()->getAccurateIntegrationParams(t, max, csd, klt); + if (state == csd) return; + _Node->getPSPointer()->setAccurateIntegrationParams(t, max, state, klt); + updateModifiedFlag(); +} + +void CParticleSystemPage::setLock(bool checked) +{ + // Need frame delay dialog. +} + +void CParticleSystemPage::setGloabal1() +{ + nlassert(_Node->getPSPointer()); + chooseGlobalUserParam(0, _Node->getPSPointer(), this); + updateModifiedFlag(); +} + +void CParticleSystemPage::setGloabal2() +{ + chooseGlobalUserParam(1, _Node->getPSPointer(), this); + updateModifiedFlag(); +} + +void CParticleSystemPage::setGloabal3() +{ + chooseGlobalUserParam(2, _Node->getPSPointer(), this); + updateModifiedFlag(); +} + +void CParticleSystemPage::setGloabal4() +{ + chooseGlobalUserParam(3, _Node->getPSPointer(), this); + updateModifiedFlag(); +} + +void CParticleSystemPage::setEnableBbox(bool state) +{ + if (state == _Node->getPSPointer()->getAutoComputeBBox()) + { + _Node->getPSPointer()->setAutoComputeBBox(!state); + updateModifiedFlag(); + } + + if (state) + updatePrecomputedBBoxParams(); + else + Modules::psEdit().setAutoBBox(false); + _ui.bboxGroupBox->setEnabled(state); +} + +void CParticleSystemPage::setAutoBbox(bool state) +{ + Modules::psEdit().setAutoBBox(state); + _ui.xDoubleSpinBox->setEnabled(!state); + _ui.yDoubleSpinBox->setEnabled(!state); + _ui.zDoubleSpinBox->setEnabled(!state); + _ui.incBboxPushButton->setEnabled(!state); + _ui.decBboxPushButton->setEnabled(!state); + updateModifiedFlag(); +} + +void CParticleSystemPage::resetBbox() +{ + Modules::psEdit().resetAutoBBox(); + updateModifiedFlag(); +} + +void CParticleSystemPage::incBbox() +{ + NLMISC::CAABBox b; + _Node->getPSPointer()->computeBBox(b); + b.setHalfSize(1.1f * b.getHalfSize()); + _Node->getPSPointer()->setPrecomputedBBox(b); + updatePrecomputedBBoxParams(); +} + +void CParticleSystemPage::decBbox() +{ + NLMISC::CAABBox b; + _Node->getPSPointer()->computeBBox(b); + b.setHalfSize(0.9f * b.getHalfSize()); + _Node->getPSPointer()->setPrecomputedBBox(b); + updatePrecomputedBBoxParams(); +} + +void CParticleSystemPage::setXBbox(double value) +{ + NLMISC::CAABBox b; + _Node->getPSPointer()->computeBBox(b); + NLMISC::CVector h; + h.x = value; + h.y = b.getHalfSize().y; + h.z = b.getHalfSize().z; + b.setHalfSize(h); + _Node->getPSPointer()->setPrecomputedBBox(b); +} + +void CParticleSystemPage::setYBbox(double value) +{ + NLMISC::CAABBox b; + _Node->getPSPointer()->computeBBox(b); + NLMISC::CVector h; + h.x = b.getHalfSize().x; + h.y = value; + h.z = b.getHalfSize().z; + b.setHalfSize(h); + _Node->getPSPointer()->setPrecomputedBBox(b); +} + +void CParticleSystemPage::setZBbox(double value) +{ + NLMISC::CAABBox b; + _Node->getPSPointer()->computeBBox(b); + NLMISC::CVector h; + h.x = b.getHalfSize().x; + h.y = b.getHalfSize().y; + h.z = value; + b.setHalfSize(h); + _Node->getPSPointer()->setPrecomputedBBox(b); +} + +void CParticleSystemPage::setEditGlobalColor(bool state) +{ + bool bGlobalColor = _Node->getPSPointer()->getColorAttenuationScheme() != NULL ? true : false; + if (state != bGlobalColor) + { + // if the system hasn't a global color scheme, add one. + if (_Node->getPSPointer()->getColorAttenuationScheme() == NULL) + { + static const NLMISC::CRGBA grad[] = { NLMISC::CRGBA::White, NLMISC::CRGBA::Black }; + _Node->getPSPointer()->setColorAttenuationScheme(new NL3D::CPSColorGradient(grad, 2, 64, 1.f)); + _ui.colorWidget->updateUi(); + } + else + { + _Node->getPSPointer()->setColorAttenuationScheme(NULL); + } + updateModifiedFlag(); + } + _ui.colorWidget->setVisible(state); +} + +void CParticleSystemPage::setPresetBehaviour(int index) +{ + updateLifeMgtPresets(); + if (index == _Node->getPSPointer()->getBehaviourType()) return; + if (index == NL3D::CParticleSystem::SpellFX || + index == NL3D::CParticleSystem::SpawnedEnvironmentFX) + { + NL3D::CPSLocatedBindable *lb; + if (!_Node->getPSPointer()->canFinish(&lb)) + { + _ui.presetBehaviourComboBox->setCurrentIndex(_Node->getPSPointer()->getBehaviourType()); + QErrorMessage *errorMessage = new QErrorMessage(); + errorMessage->setModal(true); + if (!lb) + { + errorMessage->showMessage(tr("Can't perform operation : the system is flagged with 'No max nb steps' or uses the preset 'Spell FX', " + "and thus, should have a finite duration. Please remove that flag first.")); + errorMessage->exec(); + } + else + { + errorMessage->showMessage(tr("The system must have a finite duration for this setting! Please check that the following object " + "doesn't live forever or doesn't create a loop in the system :") + QString(lb->getName().c_str())); + errorMessage->exec(); + } + delete errorMessage; + return; + } + } + _Node->getPSPointer()->activatePresetBehaviour((NL3D::CParticleSystem::TPresetBehaviour) index); + updateLifeMgtPresets(); + updateModifiedFlag(); +} + +void CParticleSystemPage::setSharable(bool state) +{ + if (state == _Node->getPSPointer()->isSharingEnabled()) return; + _Node->getPSPointer()->enableSharing(state); + updateModifiedFlag(); +} + +void CParticleSystemPage::setAutoLOD(bool state) +{ + _ui.settingsPushButton->setEnabled(state); + // performance warning + if (state == _Node->getPSPointer()->isAutoLODEnabled()) return; + _Node->getPSPointer()->enableAutoLOD(state); + updateModifiedFlag(); +} + +void CParticleSystemPage::settings() +{ + CAutoLODDialog *dialog = new CAutoLODDialog(_Node, _Node->getPSPointer(), this); + dialog->show(); + dialog->exec(); + delete dialog; +} + +void CParticleSystemPage::setModelRemoved(bool state) +{ + if (state == _Node->getPSPointer()->getDestroyModelWhenOutOfRange()) return; + _Node->getPSPointer()->setDestroyModelWhenOutOfRange(state); + updateModifiedFlag(); +} + +void CParticleSystemPage::setPSResource(bool state) +{ + if (state != _Node->getPSPointer()->doesDestroyWhenOutOfFrustum()) + { + _Node->getPSPointer()->destroyWhenOutOfFrustum(state); + updateModifiedFlag(); + } + _ui.animTypeComboBox->setEnabled(!state); +} + +void CParticleSystemPage::setLifeTimeUpdate(bool state) +{ + NL3D::TAnimationTime t; + uint32 max; + bool csd; + bool klt; + _Node->getPSPointer()->getAccurateIntegrationParams(t, max, csd, klt); + if (klt == state) return; + _Node->getPSPointer()->setAccurateIntegrationParams(t, max, csd, state); + updateModifiedFlag(); +} + +void CParticleSystemPage::setNoMaxNBSteps(bool state) +{ + _ui.maxStepsWidget->setEnabled(!state); + if (state == _Node->getPSPointer()->getBypassMaxNumIntegrationSteps()) return; + if (state && !_Node->getPSPointer()->canFinish()) + { + QErrorMessage *errorMessage = new QErrorMessage(); + errorMessage->setModal(true); + errorMessage->showMessage(tr("The system must have a finite duration for this setting! Please check that.")); + errorMessage->exec(); + delete errorMessage; + _ui.maxStepsWidget->setEnabled(state); + _ui.noMaxNBStepsCheckBox->setChecked(!state); + return; + } + _Node->getPSPointer()->setBypassMaxNumIntegrationSteps(state); + updateModifiedFlag(); +} + +void CParticleSystemPage::setAutoDelay(bool state) +{ + _ui.afterDelaySpinBox->setEnabled(!state); + if (state == _Node->getPSPointer()->getAutoComputeDelayBeforeDeathConditionTest()) return; + _Node->getPSPointer()->setAutoComputeDelayBeforeDeathConditionTest(state); + updateModifiedFlag(); +} + +void CParticleSystemPage::setAnimType(int index) +{ + if (index == _Node->getPSPointer()->getAnimType()) return; + _Node->getPSPointer()->setAnimType((NL3D::CParticleSystem::TAnimType) index); + updateModifiedFlag(); +} + +void CParticleSystemPage::setDie(int index) +{ + if (index != _Node->getPSPointer()->getDestroyCondition()) + { + _Node->getPSPointer()->setDestroyCondition((NL3D::CParticleSystem::TDieCondition) index); + updateModifiedFlag(); + } + updateDieOnEventParams(); +} + +void CParticleSystemPage::setAfterDelay(double value) +{ + if (_Node->getPSPointer()->getDelayBeforeDeathConditionTest() != value) + { + _Node->getPSPointer()->setDelayBeforeDeathConditionTest(value); + updateModifiedFlag(); + } +} + +CParticleSystemPage::CGlobalColorWrapper::scheme_type *CParticleSystemPage::CGlobalColorWrapper::getScheme(void) const +{ + nlassert(PS); + return PS->getColorAttenuationScheme(); +} + +void CParticleSystemPage::CGlobalColorWrapper::setScheme(CParticleSystemPage::CGlobalColorWrapper::scheme_type *s) +{ + PS->setColorAttenuationScheme(s); +} + +} /* namespace NLQT */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_system_page.h b/code/nel/tools/3d/object_viewer_qt/src/particle_system_page.h new file mode 100644 index 000000000..9680b2508 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_system_page.h @@ -0,0 +1,156 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_SYSTEM_PAGE_H +#define PARTICLE_SYSTEM_PAGE_H + +#include +#include "ui_particle_system_form.h" + +// STL includes + +// NeL includes +#include +#include +#include "ps_wrapper.h" + +// Project includes +#include "edit_range_widget.h" + +namespace NLQT { + +class CUserParamWrapper : public IPSWrapperFloat +{ +public: + NL3D::CParticleSystem *PS; + uint32 Index; + float get(void) const; + void set(const float &v); +}; + +class CTimeThresholdWrapper : public IPSWrapperFloat +{ +public: + NL3D::CParticleSystem *PS; + float get(void) const; + void set(const float &); +}; + +class CMaxNbIntegrationWrapper : public IPSWrapperUInt +{ +public: + NL3D::CParticleSystem *PS; + uint32 get(void) const; + void set(const uint32 &); +}; + +class CMaxViewDistWrapper : public IPSWrapperFloat +{ +public: + NL3D::CParticleSystem *PS; + float get(void) const; + void set(const float &); +}; + +class CLODRatioWrapper : public IPSWrapperFloat +{ +public: + NL3D::CParticleSystem *PS; + float get(void) const; + void set(const float &); +}; + +/** +@class CParticleSystemPage +@brief Page for QStackWidget, to edit workspace node in a particle system +*/ +class CParticleSystemPage: public QWidget +{ + Q_OBJECT + +public: + CParticleSystemPage(QWidget *parent = 0); + ~CParticleSystemPage(); + + /// Set the workspace node to edit. + void setEditedParticleSystem(CWorkspaceNode *node); + +private Q_SLOTS: + void setGlobalLight(bool state); + void setLoadBalancing(bool state); + void setIntegration(bool state); + void setMotionSlowDown(bool state); + void setLock(bool checked); + void setGloabal1(); + void setGloabal2(); + void setGloabal3(); + void setGloabal4(); + void setEnableBbox(bool state); + void setAutoBbox(bool state); + void resetBbox(); + void incBbox(); + void decBbox(); + void setXBbox(double value); + void setYBbox(double value); + void setZBbox(double value); + void setEditGlobalColor(bool state); + void setPresetBehaviour(int index); + void setSharable(bool state); + void setAutoLOD(bool state); + void settings(); + void setModelRemoved(bool state); + void setPSResource(bool state); + void setLifeTimeUpdate(bool state); + void setNoMaxNBSteps(bool state); + void setAutoDelay(bool state); + void setAnimType(int index); + void setDie(int index); + void setAfterDelay(double value); + +private: + void updatePrecomputedBBoxParams(); + void updateDieOnEventParams(); + void updateLifeMgtPresets(); + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + CWorkspaceNode *_Node; + CTimeThresholdWrapper _TimeThresholdWrapper; + CMaxViewDistWrapper _MaxViewDistWrapper; + CMaxNbIntegrationWrapper _MaxNbIntegrationWrapper; + CLODRatioWrapper _LODRatioWrapper; + + /// wrapper to tune user parameters + CUserParamWrapper _UserParamWrapper[NL3D::MaxPSUserParam]; + + struct CGlobalColorWrapper : public IPSSchemeWrapperRGBA + { + NL3D::CParticleSystem *PS; + virtual scheme_type *getScheme(void) const; + virtual void setScheme(scheme_type *s); + } + _GlobalColorWrapper; + + Ui::CParticleSystemPage _ui; + +}; /* class CParticleSystemPage */ + +} /* namespace NLQT */ + +#endif // PARTICLE_SYSTEM_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_form.ui new file mode 100644 index 000000000..453868813 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_form.ui @@ -0,0 +1,110 @@ + + + CParticleTextureAnimWidget + + + + 0 + 0 + 306 + 186 + + + + Form + + + + + + Enable texture animation + + + + + + + + 0 + 0 + + + + + + + + + + Multitexturing + + + + + + + false + + + Edit + + + + + + + + + + true + + + Texture grouped + + + true + + + + + + + + + + + + + + NLQT::CAttribIntWidget + QWidget +
attrib_widget.h
+ 1 +
+ + NLQT::CParticleTextureWidget + QWidget +
particle_texture_widget.h
+ 1 +
+
+ + + + multitexturingCheckBox + toggled(bool) + editPushButton + setEnabled(bool) + + + 81 + 46 + + + 129 + 56 + + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.cpp new file mode 100644 index 000000000..243304250 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.cpp @@ -0,0 +1,151 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_texture_anim_widget.h" + +// NeL includes +#include +#include +#include +#include +#include + +// Projects includes +#include "value_gradient_dialog.h" +#include "multi_tex_dialog.h" + +namespace NLQT { + +CParticleTextureAnimWidget::CParticleTextureAnimWidget(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.texIndexWidget->setRange(0, 1); + _ui.texIndexWidget->setWrapper(&_TextureIndexWrapper ); + _ui.texIndexWidget->setSchemeWrapper(&_TextureIndexWrapper ); + _ui.texIndexWidget->init(); + + _ui.texWidget->setWrapper(&_TextureWrapper); + + connect(_ui.texAnimCheckBox, SIGNAL(toggled(bool)), this, SLOT(setEnabledTexAnim(bool))); + connect(_ui.multitexturingCheckBox, SIGNAL(toggled(bool)), this, SLOT(setMultitexturing(bool))); + connect(_ui.editPushButton, SIGNAL(clicked()), this, SLOT(editMultitexturing())); + connect(_ui.textureGroupedPushButton, SIGNAL(clicked()), this, SLOT(chooseGroupedTexture())); +} + +CParticleTextureAnimWidget::~CParticleTextureAnimWidget() +{ +} + +void CParticleTextureAnimWidget::setCurrentTextureAnim(NL3D::CPSTexturedParticle *tp, NL3D::CPSMultiTexturedParticle *mtp,CWorkspaceNode *ownerNode) +{ + _Node = ownerNode; + _EditedParticle = tp; + _MTP = mtp; + + _ui.texAnimCheckBox->blockSignals(true); + + bool isAnimTex = _EditedParticle->getTextureGroup() ? true : false; + _ui.texAnimCheckBox->setChecked(isAnimTex); + updateTexAnimState(isAnimTex); + + if (_MTP) + { + _ui.multitexturingCheckBox->setChecked(_MTP->isMultiTextureEnabled()); + _ui.multitexturingGroupBox->show(); + } + else + _ui.multitexturingGroupBox->hide(); + + _ui.texAnimCheckBox->blockSignals(false); +} + +void CParticleTextureAnimWidget::setEnabledTexAnim(bool state) +{ + if (state) + { + // When you try to load a dummy texture, remove alternative paths, an assertion is thrown otherwise + NLMISC::CPath::removeAllAlternativeSearchPath(); + + // put a dummy texture as a first texture + NLMISC::CSmartPtr tex = (NL3D::ITexture *) new NL3D::CTextureFile(std::string("")); + NL3D::CTextureGrouped *tg = new NL3D::CTextureGrouped; + tg->setTextures(&tex, 1); + _EditedParticle->setTextureGroup(tg); + _EditedParticle->setTextureIndex(0); + } + else + { + _EditedParticle->setTexture(NULL); + } + updateTexAnimState(state); + updateModifiedFlag(); +} + +void CParticleTextureAnimWidget::chooseGroupedTexture() +{ + CTextureGradientInterface *texInterface = new CTextureGradientInterface(_EditedParticle, _Node); + CGradientDialog *gd = new CGradientDialog(_Node, texInterface, true, false, 1, this); + gd->setModal(true); + gd->show(); + gd->exec(); + delete gd; +} + +void CParticleTextureAnimWidget::setMultitexturing(bool enabled) +{ + if (_MTP->isMultiTextureEnabled() != enabled) + { + _MTP->enableMultiTexture(enabled); + updateModifiedFlag(); + } +} + +void CParticleTextureAnimWidget::editMultitexturing() +{ + CMultiTexDialog *multiTexDialog = new CMultiTexDialog(_Node, _MTP, this); + multiTexDialog->show(); + multiTexDialog->exec(); + delete multiTexDialog; +} + +void CParticleTextureAnimWidget::updateTexAnimState(bool state) +{ + if (state) + { + _ui.texIndexWidget->setRange( 0, _EditedParticle->getTextureGroup()->getNbTextures() - 1); + _TextureIndexWrapper.P = _EditedParticle; + _ui.texIndexWidget->setWorkspaceNode(_Node); + _ui.texIndexWidget->updateUi(); + } + else + { + _TextureWrapper.P = _EditedParticle; + _TextureWrapper.OwnerNode = _Node; + + _ui.texWidget->updateUi(); + } + _ui.texWidget->setVisible(!state); + _ui.textureGroupedPushButton->setVisible(state); + _ui.texIndexWidget->setVisible(state); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.h b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.h new file mode 100644 index 000000000..cf8cfe693 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_anim_widget.h @@ -0,0 +1,93 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_TEXTURE_ANIM_WIDGET_H +#define PARTICLE_TEXTURE_ANIM_WIDGET_H + +#include "ui_particle_texture_anim_form.h" + +// STL includes + +// Qt includes + +// NeL includes +#include "nel/3d/ps_particle.h" + +// Project includes +#include "ps_wrapper.h" + +namespace NL3D +{ + class CPSTexturedParticle; + class CPSMultiTexturedParticle; +} + +namespace NLQT { + +class CParticleTextureAnimWidget: public QWidget +{ + Q_OBJECT + +public: + CParticleTextureAnimWidget(QWidget *parent = 0); + ~CParticleTextureAnimWidget(); + + void setCurrentTextureAnim(NL3D::CPSTexturedParticle *tp, NL3D::CPSMultiTexturedParticle *mtp,CWorkspaceNode *ownerNode); + +private Q_SLOTS: + void chooseGroupedTexture(); + void setEnabledTexAnim(bool state); + void setMultitexturing(bool enabled); + void editMultitexturing(); + +private: + void updateTexAnimState(bool state); + + /// Wrapper for single texture + struct CTextureWrapper : public IPSWrapperTexture + { + NL3D::CPSTexturedParticle *P; + NL3D::ITexture *get(void) { return P->getTexture(); } + void set(NL3D::ITexture *t) { P->setTexture(t); } + } _TextureWrapper; + + /// Wrapper for texture anim sequence + struct CTextureIndexWrapper : public IPSWrapper, IPSSchemeWrapper + { + NL3D::CPSTexturedParticle *P; + sint32 get(void) const { return P->getTextureIndex(); } + void set(const sint32 &v) { P->setTextureIndex(v); } + scheme_type *getScheme(void) const { return P->getTextureIndexScheme(); } + void setScheme(scheme_type *s) { P->setTextureIndexScheme(s); } + } _TextureIndexWrapper; + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + NL3D::CPSTexturedParticle *_EditedParticle; + + NL3D::CPSMultiTexturedParticle *_MTP; + + CWorkspaceNode *_Node; + + Ui::CParticleTextureAnimWidget _ui; +}; /* class CParticleTextureAnimWidget */ + +} /* namespace NLQT */ + +#endif // PARTICLE_TEXTURE_ANIM_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_texture_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_form.ui new file mode 100644 index 000000000..564bfe7c7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_form.ui @@ -0,0 +1,107 @@ + + + CParticleTextureWidget + + + + 0 + 0 + 191 + 162 + + + + Form + + + + + + + 100 + 100 + + + + + 100 + 100 + + + + true + + + + + 0 + 0 + 94 + 94 + + + + + + + + 85 + 85 + + + + + + + + + + + + + + + + 0 + 0 + + + + Choose texture + + + + + + + + 0 + 0 + + + + Remove + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 7 + 20 + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.cpp new file mode 100644 index 000000000..1f40900f2 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.cpp @@ -0,0 +1,126 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_texture_widget.h" + +// Qt include +#include +#include + +// NeL includes +#include "nel/3d/texture_file.h" +#include "nel/3d/texture_bump.h" +#include "nel/3d/ps_particle_basic.h" +#include "nel/misc/path.h" + +namespace NLQT { + +CParticleTextureWidget::CParticleTextureWidget(QWidget *parent) + : QWidget(parent), _Wrapper(NULL) +{ + _ui.setupUi(this); + + _ui.imageLabel->setScaledContents(true); + _ui.removePushButton->setVisible(false); + + connect(_ui.chooseTexPushButton, SIGNAL(clicked()), this, SLOT(chooseTexture())); + connect(_ui.removePushButton, SIGNAL(clicked()), this, SLOT(removeTexture())); +} + +CParticleTextureWidget::~CParticleTextureWidget() +{ +} + +void CParticleTextureWidget::updateUi() +{ + nlassert(_Wrapper); + + _Texture = _Wrapper->get(); + + updateTexture(); +} + +void CParticleTextureWidget::chooseTexture() +{ + std::string texName; + /// get the name of the previously set texture if there is one + if (dynamic_cast(_Wrapper->get())) + { + if (!texName.empty()) + texName = NLMISC::CPath::lookup((static_cast(_Wrapper->get()))->getFileName()); + } + + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open texture file"), texName.c_str(), + tr("Image file (*.tga *.png)")); + + setCursor(Qt::WaitCursor); + if (!fileName.isEmpty()) + { + // Add search path for the texture + NLMISC::CPath::addSearchPath(NLMISC::CFile::getPath(fileName.toStdString())); + try + { + texName = NLMISC::CFile::getFilename(fileName.toStdString()); + NL3D::CTextureFile *tf = new NL3D::CTextureFile(texName); + _Wrapper->setAndUpdateModifiedFlag(tf); + _Texture = tf; + Q_EMIT textureChanged(QString(texName.c_str())); + updateTexture(); + } + catch (NLMISC::Exception &e) + { + QMessageBox::critical(this, tr("Texture loading error"), e.what(), QMessageBox::Ok); + } + + } + setCursor(Qt::ArrowCursor); +} + +void CParticleTextureWidget::removeTexture() +{ + if (_Texture) + { + _Texture->release(); + _Texture = NULL; + } + _Wrapper->setAndUpdateModifiedFlag(NULL); + Q_EMIT textureChanged(""); + updateTexture(); +} + +void CParticleTextureWidget::updateTexture() +{ + if (!_Texture) + { + _ui.imageLabel->setPixmap(QPixmap()); + return; + } + if (dynamic_cast(_Wrapper->get())) + { + std::string texName = (static_cast(_Wrapper->get()))->getFileName().c_str(); + if (!NLMISC::CFile::getFilename(texName).empty()) + _ui.imageLabel->setPixmap(QPixmap(NLMISC::CPath::lookup(texName).c_str())); + else + _ui.imageLabel->setPixmap(QPixmap()); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.h b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.h new file mode 100644 index 000000000..f1950a46f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_texture_widget.h @@ -0,0 +1,74 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_TEXTURE_WIDGET_H +#define PARTICLE_TEXTURE_WIDGET_H + +#include "ui_particle_texture_form.h" + +// STL includes + +// Qt includes + +// NeL includes +#include +#include +#include + +// Project includes +#include "ps_wrapper.h" + +namespace NLQT { + +class CParticleTextureWidget: public QWidget +{ + Q_OBJECT + +public: + CParticleTextureWidget(QWidget *parent = 0); + ~CParticleTextureWidget(); + + void updateUi(); + + // set a wrapper to get the datas, called before setCurrentTextureNoAnim + void setWrapper(IPSWrapperTexture *wrapper) { _Wrapper = wrapper ; } + + void enableRemoveButton(bool enabled) { _ui.removePushButton->setVisible(enabled); } + +Q_SIGNALS: + void textureChanged(const QString &texName); + +private Q_SLOTS: + void chooseTexture(); + void removeTexture(); + +private: + void updateTexture(); + + IPSWrapperTexture *_Wrapper ; + + // the current texture + NLMISC::CSmartPtr _Texture ; + + Ui::CParticleTextureWidget _ui; +}; /* class CParticleTextureWidget */ + +} /* namespace NLQT */ + +#endif // PARTICLE_TEXTURE_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.cpp new file mode 100644 index 000000000..b12d03624 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.cpp @@ -0,0 +1,537 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_tree_model.h" + +// Qt includes + +// NeL includes +#include "nel/3d/particle_system.h" + +// Project includes +#include "modules.h" + +namespace NLQT { + +CParticleTreeItem::CParticleTreeItem(const QList &data, const int typeItem, CParticleTreeItem *parent): + _itemData(data), _itemIconType(typeItem), _parentItem(parent) +{ + +} + +CParticleTreeItem::CParticleTreeItem(CParticleWorkspace *ws, const QList &data, const int typeItem, CParticleTreeItem *parent): + _itemData(data), _itemIconType(typeItem), _parentItem(parent) +{ + nlassert(ws); + _WS = ws; +} + +CParticleTreeItem::CParticleTreeItem(NL3D::CPSLocated *loc, const QList &data, const int typeItem, CParticleTreeItem *parent): + _itemData(data), _itemIconType(typeItem), _parentItem(parent) +{ + nlassert(loc); + _Loc = loc; +} + +CParticleTreeItem::CParticleTreeItem(NL3D::CPSLocated *loc, uint32 index, const QList &data, const int typeItem, CParticleTreeItem *parent): + _itemData(data), _itemIconType(typeItem), _parentItem(parent) +{ + nlassert(loc); + _Loc = loc; + _LocatedInstanceIndex = index; +} + +CParticleTreeItem::CParticleTreeItem(CWorkspaceNode *node, const QList &data, const int typeItem, CParticleTreeItem *parent): + _itemData(data), _itemIconType(typeItem), _parentItem(parent) +{ + _PS = node; +} + +CParticleTreeItem::CParticleTreeItem(NL3D::CPSLocatedBindable *lb, const QList &data, const int typeItem, CParticleTreeItem *parent): + _itemData(data), _itemIconType(typeItem), _parentItem(parent) +{ + _Bind = lb; +} + +CParticleTreeItem::~CParticleTreeItem() +{ + qDeleteAll(_childItems); +} + +void CParticleTreeItem::appendChild(CParticleTreeItem *child) +{ + _childItems.append(child); +} + +CParticleTreeItem *CParticleTreeItem::child(int row) +{ + return _childItems.value(row); +} + +int CParticleTreeItem::childCount() const +{ + return _childItems.count(); +} + +int CParticleTreeItem::columnCount() const +{ + return _itemData.count(); +} + +int CParticleTreeItem::itemType() const +{ + return _itemIconType; +} + +QVariant CParticleTreeItem::data(int column) const +{ + return _itemData.value(column); +} + +CParticleTreeItem *CParticleTreeItem::parent() +{ + return _parentItem; +} + +int CParticleTreeItem::row() const +{ + if (_parentItem) + return _parentItem->_childItems.indexOf(const_cast(this)); + return 0; +} + +bool CParticleTreeItem::replace(const QList &data) +{ + _itemData = data; + return true; +} + +bool CParticleTreeItem::deleteChild(int row) +{ + _childItems.removeAt(row); + return true; +} + +CParticleWorkspace *CParticleTreeItem::getPW() const +{ + return _WS; +} + +NL3D::CPSLocated *CParticleTreeItem::getLoc() const +{ + return _Loc; +} + +NL3D::CPSLocatedBindable *CParticleTreeItem::getBind() const +{ + return _Bind; +} + +CWorkspaceNode *CParticleTreeItem::getNode() const +{ + return _PS; +} + +uint32 CParticleTreeItem::getLocatedInstanceIndex() const +{ + return _LocatedInstanceIndex; +} + +void CParticleTreeItem::setLocatedInstanceIndex(uint32 index) +{ + _LocatedInstanceIndex = index; +} + +CParticleTreeModel::CParticleTreeModel(QObject *parent) + : QAbstractItemModel(parent) +{ + QList rootData; + rootData << "Name"; + _rootItem = new CParticleTreeItem(rootData, ItemType::Root); +} + +CParticleTreeModel::~CParticleTreeModel() +{ + delete _rootItem; +} + +int CParticleTreeModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return static_cast(parent.internalPointer())->columnCount(); + else + return _rootItem->columnCount(); +} + +QVariant CParticleTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + CParticleTreeItem *item = static_cast(index.internalPointer()); + if (item->itemType() == ItemType::ParticleSystem) + { + if (item->getNode()->isModified()) + return "*" + item->data(index.column()).toString(); + } + if (item->itemType() == ItemType::Workspace) + { + if (item->getPW()->isModified()) + return "*" + item->data(index.column()).toString(); + } + return item->data(index.column()); + } + if (role == Qt::FontRole) + { + CParticleTreeItem *item = static_cast(index.internalPointer()); + if (Modules::psEdit().getActiveNode() != NULL) + { + if (item->data(0) == QString(Modules::psEdit().getActiveNode()->getFilename().c_str())) + return QFont("SansSerif", 9, QFont::Bold); + } + return QFont("SansSerif", 9, QFont::Normal); + } + if (role == Qt::DecorationRole) + return qVariantFromValue(getIcon(index)); + + return QVariant(); +} + +Qt::ItemFlags CParticleTreeModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags itmFlags = Qt::NoItemFlags; + + if (!index.isValid()) + return itmFlags; + + CParticleTreeItem *item = static_cast(index.internalPointer()); + switch (item->itemType()) + { + case ItemType::Particle: + case ItemType::Emitter: + case ItemType::Force: + case ItemType::Light: + case ItemType::Sound: + case ItemType::Located: + case ItemType::CollisionZone: + itmFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; + break; + case ItemType::LocatedInstance: + if (Modules::psEdit().isRunning()) + itmFlags = Qt::NoItemFlags; + else + itmFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + break; + default: + itmFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + break; + } + + //CParticleWorkspaceDialog *pwsd = qobject_cast(QObject::parent()); + //pwsd->updateTreeView(); + + return itmFlags; +} + +QVariant CParticleTreeModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return _rootItem->data(section); + + return QVariant(); +} + +QModelIndex CParticleTreeModel::index(int row, int column, const QModelIndex &parent) + const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + CParticleTreeItem *parentItem; + + if (!parent.isValid()) + parentItem = _rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + CParticleTreeItem *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex CParticleTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + CParticleTreeItem *childItem = static_cast(index.internalPointer()); + CParticleTreeItem *parentItem = childItem->parent(); + + if (parentItem == _rootItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +int CParticleTreeModel::rowCount(const QModelIndex &parent) const +{ + CParticleTreeItem *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = _rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->childCount(); +} + +bool CParticleTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole) + { + CParticleTreeItem *item = static_cast(index.internalPointer()); + QList listData; + listData << value; + item->replace(listData); + std::string name = value.toString().toStdString(); + if (item->itemType() == ItemType::Located) + item->getLoc()->setName(name); + else + item->getBind()->setName(name); + Q_EMIT dataChanged(index, index); + return true; + } + return false; +} + +QIcon CParticleTreeModel::getIcon(const QModelIndex &index) const +{ + CParticleTreeItem *item = static_cast(index.internalPointer()); + switch (item->itemType()) + { + case ItemType::Root: + break; + case ItemType::Workspace: + return QIcon(":/images/Workspace.bmp"); + case ItemType::ParticleSystem: + return QIcon(":/images/ParticleSystem.bmp"); + case ItemType::Particle: + return QIcon(":/images/Particle.bmp"); + case ItemType::Emitter: + return QIcon(":/images/Emitter.bmp"); + case ItemType::Force: + return QIcon(":/images/Force.bmp"); + case ItemType::Light: + return QIcon(":/images/Light.bmp"); + case ItemType::Sound: + return QIcon(":/images/Sound.bmp"); + case ItemType::Located: + return QIcon(":/images/Located.bmp"); + case ItemType::CollisionZone: + return QIcon(":/images/CollisionZone.bmp"); + case ItemType::LocatedInstance: + return QIcon(":/images/LocatedInstance.bmp"); + case ItemType::ParticleSystemNotLoaded: + return QIcon(":/images/ParticleSystemNotLoaded.bmp"); + } + return QIcon(); +} + +bool CParticleTreeModel::insertRows(CWorkspaceNode *node, int position, const QModelIndex &parent) +{ + CParticleTreeItem *item = static_cast(parent.internalPointer()); + + beginInsertRows(parent, position, position); + setupModelFromPS(node, item); + endInsertRows(); + + if (node->isLoaded()) + { + QModelIndex indexPS = index(item->childCount() - 1, 0, parent); + for (uint k = 0; k < node->getPSPointer()->getNbProcess(); k++) + { + NL3D::CPSLocated *loc = dynamic_cast(node->getPSPointer()->getProcess(k)); + insertRows(loc, k, indexPS); + } + } + return true; +} + +bool CParticleTreeModel::insertRows(NL3D::CPSLocated *loc, int position, const QModelIndex &parent) +{ + CParticleTreeItem *item = static_cast(parent.internalPointer()); + + beginInsertRows(parent, position, position); + createItemFromLocated(loc, item); + endInsertRows(); + + QModelIndex indexLocated = index(item->childCount() - 1, 0, parent); + for (uint l = 0; l < loc->getNbBoundObjects(); ++l) + insertRow(loc->getBoundObject(l), l, indexLocated); + + for (uint k = 0; k < loc->getSize(); ++k) + insertRow(loc, k, k + loc->getNbBoundObjects(), indexLocated); + + return true; +} + +bool CParticleTreeModel::insertRow(NL3D::CPSLocated *loc, uint32 index, int position, const QModelIndex &parent) +{ + beginInsertRows(parent, position, position); + createItemFromLocatedInstance(loc, index, static_cast(parent.internalPointer())); + endInsertRows(); + return true; +} + +bool CParticleTreeModel::insertRow(NL3D::CPSLocatedBindable *lb, int position, const QModelIndex &parent) +{ + beginInsertRows(parent, position, position); + createItemFromLocatedBindable(lb, static_cast(parent.internalPointer())); + endInsertRows(); + return true; +} + +bool CParticleTreeModel::removeRows(int position, const QModelIndex &parent) +{ + CParticleTreeItem *item = static_cast(parent.internalPointer())->child(position); + while (item->childCount() != 0) + removeRows(0, parent.child(position, 0)); + + beginRemoveRows(parent, position, position); + static_cast(parent.internalPointer())->deleteChild(position); + endRemoveRows(); + return false; +} + +CWorkspaceNode *CParticleTreeModel::getOwnerNode(CParticleTreeItem *item) const +{ + CWorkspaceNode *node = NULL; + switch (item->itemType()) + { + case ItemType::ParticleSystem: + node = item->getNode(); + break; + case ItemType::Located: + node = item->parent()->getNode(); + break; + case ItemType::LocatedInstance: + case ItemType::Particle: + case ItemType::Emitter: + case ItemType::Force: + case ItemType::Light: + case ItemType::Sound: + case ItemType::CollisionZone: + node = item->parent()->parent()->getNode(); + break; + } + return node; +} + +void CParticleTreeModel::rebuildLocatedInstance(const QModelIndex &parent) +{ + CParticleTreeItem *item = static_cast(parent.internalPointer()); + int k = 0; + for (int i = 0; i < item->childCount(); i++) + { + if (item->child(i)->itemType() == ItemType::LocatedInstance) + { + item->child(i)->setLocatedInstanceIndex(k); + k++; + } + } +} + +void CParticleTreeModel::setupModelFromWorkSpace() +{ + beginResetModel(); + delete _rootItem; + + QList rootData; + rootData << "Name"; + _rootItem = new CParticleTreeItem(rootData, ItemType::Root); + + QList workspaceData; + CParticleWorkspace *workspace = Modules::psEdit().getParticleWorkspace(); + + if (workspace == NULL) + { + endResetModel(); + return; + } + + workspaceData << workspace->getFilename().c_str(); + CParticleTreeItem *parent = new CParticleTreeItem(workspace ,workspaceData, ItemType::Workspace, _rootItem); + _rootItem->appendChild(parent); + endResetModel(); + + QModelIndex rootIndex = index(0, 0); + uint numNode = workspace->getNumNode(); + for (uint i = 0; i < numNode; i++) + insertRows(workspace->getNode(i), i, rootIndex); +} + +void CParticleTreeModel::setupModelFromPS(CWorkspaceNode *node, CParticleTreeItem *parent) +{ + QList particleSystemData; + particleSystemData << node->getFilename().c_str(); + CParticleTreeItem *child; + if (node->isLoaded()) + { + child = new CParticleTreeItem(node, particleSystemData, ItemType::ParticleSystem, parent); + parent->appendChild(child); + } + else + { + child = new CParticleTreeItem(node, particleSystemData, ItemType::ParticleSystemNotLoaded, parent); + parent->appendChild(child); + } +} + +void CParticleTreeModel::createItemFromLocated(NL3D::CPSLocated *loc, CParticleTreeItem *parent) +{ + QList locatedData; + locatedData << QString(loc->getName().c_str()); + CParticleTreeItem *child = new CParticleTreeItem(loc, locatedData, ItemType::Located, parent); + parent->appendChild(child); +} + +void CParticleTreeModel::createItemFromLocatedInstance(NL3D::CPSLocated *loc, uint32 index, CParticleTreeItem *parent) +{ + QList locatedData; + locatedData << QString("instance"); + CParticleTreeItem *child = new CParticleTreeItem(loc, index, locatedData, ItemType::LocatedInstance, parent); + parent->appendChild(child); +} + +void CParticleTreeModel::createItemFromLocatedBindable(NL3D::CPSLocatedBindable *lb, CParticleTreeItem *parent) +{ + QList forceData; + forceData << lb->getName().c_str(); + parent->appendChild(new CParticleTreeItem(lb, forceData, lb->getType(), parent)); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.h b/code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.h new file mode 100644 index 000000000..ae8d82984 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_tree_model.h @@ -0,0 +1,184 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_TREE_MODEL_H +#define PARTICLE_TREE_MODEL_H + +// Qt includes +#include +#include +#include +#include +#include + +// NeL includes +#include "nel/misc/types_nl.h" +#include "nel/3d/ps_located.h" + +// Projects includes +#include "particle_node.h" + +namespace NLQT { + +struct ItemType +{ + enum List + { + Force = 0, + Particle, + Emitter, + Light, + CollisionZone, + Sound, + Root, + Workspace, + ParticleSystem, + Located, + LocatedInstance, + ParticleSystemNotLoaded + }; +}; + +/** +@class CParticleTreeItem +@brief Basic elements tree model particles workspace. +Сontains pointer to items particle workspace and type icons. +*/ +class CParticleTreeItem +{ +public: + CParticleTreeItem(const QList &data, const int typeItem, CParticleTreeItem *parent = 0); + + CParticleTreeItem(CParticleWorkspace *ws, const QList &data, const int typeItem, CParticleTreeItem *parent ); + + CParticleTreeItem(NL3D::CPSLocated *loc, const QList &data, const int typeItem, CParticleTreeItem *parent); + + CParticleTreeItem(NL3D::CPSLocated *loc, uint32 index, const QList &data, const int typeItem, CParticleTreeItem *parent); + + CParticleTreeItem(CWorkspaceNode *node, const QList &data, const int typeItem, CParticleTreeItem *parent); + + CParticleTreeItem(NL3D::CPSLocatedBindable *lb, const QList &data, const int typeItem, CParticleTreeItem *parent); + + ~CParticleTreeItem(); + + void appendChild(CParticleTreeItem *child); + + CParticleTreeItem *child(int row); + int childCount() const; + int columnCount() const; + int itemType() const; + QVariant data(int column) const; + int row() const; + CParticleTreeItem *parent(); + bool replace(const QList &data); + bool deleteChild(int row); + CParticleWorkspace *getPW() const; + NL3D::CPSLocated *getLoc() const; + NL3D::CPSLocatedBindable *getBind() const; + CWorkspaceNode *getNode() const; + uint32 getLocatedInstanceIndex() const; + void setLocatedInstanceIndex(uint32 index); + +private: + union + { + CParticleWorkspace *_WS; + NL3D::CPSLocated *_Loc; + NL3D::CPSLocatedBindable *_Bind; + CWorkspaceNode *_PS; + }; + + // for the located instance type, this is the index of the instance + uint32 _LocatedInstanceIndex; + + QList _childItems; + QList _itemData; + int _itemIconType; + CParticleTreeItem *_parentItem; +}; + +/** +@class CParticleTreeModel +@brief Tree model particles workspace. +*/ +class CParticleTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + CParticleTreeModel(QObject *parent = 0); + ~CParticleTreeModel(); + + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QIcon getIcon(const QModelIndex &index) const; + + /// Insert WorkspaceNode in model and add all its sub-item. + bool insertRows(CWorkspaceNode *node, int position, const QModelIndex &parent = QModelIndex()); + + /// Insert Located item in model and add all its sub-item. + bool insertRows(NL3D::CPSLocated *loc, int position, const QModelIndex &parent = QModelIndex()); + + /// Insert Located item in model. + bool insertRow(NL3D::CPSLocated *loc, uint32 index, int position, const QModelIndex &parent = QModelIndex()); + + /// Insert LocatedBindable item in model. + bool insertRow(NL3D::CPSLocatedBindable *lb, int position, const QModelIndex &parent = QModelIndex()); + + /// Deletes a tree item and all its children. + bool removeRows(int position, const QModelIndex &parent = QModelIndex()); + + /// Get the parent node in the workspace for the given element in the tree + CWorkspaceNode *getOwnerNode(CParticleTreeItem *item) const; + + /// Rebuild the located instance in the tree (after loading for example) + void rebuildLocatedInstance(const QModelIndex &parent); + + /// Build the whole tree from a workspace + void setupModelFromWorkSpace(); + +private: + /// Build a portion of the tree using the given particle system + void setupModelFromPS(CWorkspaceNode *node, CParticleTreeItem *parent); + + /// Add item from the given located + void createItemFromLocated(NL3D::CPSLocated *loc, CParticleTreeItem *parent); + + /// Add item from the given located instance + void createItemFromLocatedInstance(NL3D::CPSLocated *loc, uint32 index, CParticleTreeItem *parent); + + /// Add item from the given located bindable + void createItemFromLocatedBindable(NL3D::CPSLocatedBindable *lb, CParticleTreeItem *parent); + + CParticleTreeItem *_rootItem; +}; + +} /* namespace NLQT */ + +#endif // PARTICLE_TREE_MODEL_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.cpp new file mode 100644 index 000000000..99db046a6 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.cpp @@ -0,0 +1,767 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_workspace_dialog.h" + +// NeL includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Qt includes +#include +#include +#include +#include + +// Project includes +#include "dup_ps.h" +#include "modules.h" + +namespace NLQT { + +static const char * const LocatedBindable[] = { + QT_TR_NOOP("Point"), + QT_TR_NOOP("LookAt"), + QT_TR_NOOP("FanLight"), + QT_TR_NOOP("Ribbon"), + QT_TR_NOOP("TailDot"), + QT_TR_NOOP("Mesh"), + QT_TR_NOOP("ConstraintMesh"), + QT_TR_NOOP("Face"), + QT_TR_NOOP("ShockWave"), + QT_TR_NOOP("Ribbon look at"), + QT_TR_NOOP("Gravity"), + QT_TR_NOOP("Directional force"), + QT_TR_NOOP("Spring"), + QT_TR_NOOP("Flyid friction"), + QT_TR_NOOP("Central gravity"), + QT_TR_NOOP("Cylindric vortex"), + QT_TR_NOOP("Brownian move"), + QT_TR_NOOP("Magnetic force"), + QT_TR_NOOP("Plane"), + QT_TR_NOOP("Sphere"), + QT_TR_NOOP("Rectangle"), + QT_TR_NOOP("Disc"), + QT_TR_NOOP("Cylinder"), + QT_TR_NOOP("Directional"), + QT_TR_NOOP("Omni directional"), + QT_TR_NOOP("Rectangle"), + QT_TR_NOOP("Conic"), + QT_TR_NOOP("Spherical"), + QT_TR_NOOP("Radial"), + QT_TR_NOOP("Bind sound"), + QT_TR_NOOP("Bind light"), + 0 +}; + +struct Action +{ + enum List + { + ParticlePoint = 0, + ParticleLookAt, + ParticleFanLight, + ParticleRibbon, + ParticleTailDot, + ParticleMesh, + ParticleConstraintMesh, + ParticleFace, + ParticleShockWave, + ParticleRibbonLookAt, + ForceGravity, + ForceDirectional, + ForceSpring, + ForceFlyidFriction, + ForceCentralGravity, + ForceCylindricVortex, + ForceBrownianMove, + ForceMagnetic, + ZonePlane, + ZoneSphere, + ZoneRectangle, + ZoneDisc, + ZoneCylinder, + EmitterDirectional, + EmitterOmniDirectional, + EmitterRectangle, + EmitterConic, + EmitterSpherical, + EmitterRadial, + Sound, + Light + }; +}; + +// this map is used to create increasing names +static std::map _PSElementIdentifiers; + +CParticleWorkspaceDialog::CParticleWorkspaceDialog(QWidget *parent) + : QDockWidget(parent), _currentItem(NULL) +{ + _ui.setupUi(this); + + connect(_ui.treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(clickedItem(QModelIndex))); + connect(_ui.treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenu())); + + // Init tree model + _treeModel = new CParticleTreeModel(this); + _ui.treeView->setModel(_treeModel); + _ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu); + + _PropertyDialog = new CPropertyDialog(_treeModel, this); + + _signalMapper = new QSignalMapper(this); + + _setActivePSAction = new QAction(tr("Set as active particle system"), this); + _savePSAction = new QAction(tr("Save"), this); + _saveAsPSAction = new QAction(tr("Save as"), this); + _clearContentAction = new QAction(tr("Clear content"), this); + _removeFromWSAction = new QAction(tr("Remove from workspace"), this); + _mergeAction = new QAction(tr("Merge"), this); + + _newLocatedAction = new QAction(tr("New located"), this); + _pasteLocatedAction = new QAction(tr("Paste located"), this); + + for(int i = 0; LocatedBindable[i]; ++i) + _bindNewLocatedBindable[i] = new QAction(tr(LocatedBindable[i]), this); + + _forceZBiasAction = new QAction(tr("Force ZBias"), this); + + _instanciateAction = new QAction(tr("Instanciate"), this); + _copyLocatedAction = new QAction(tr("Copy located"), this); + _copyBindableAction = new QAction(tr("Copy bindable"), this); + _pasteBindableAction = new QAction(tr("Paste bindable"), this); + _deleteAction = new QAction(tr("Delete"), this); + + _allLODAction = new QAction(tr("All LOD"), this); + _allLODAction->setCheckable(true); + _lod1Action = new QAction(tr("LOD 1"), this); + _lod1Action->setCheckable(true); + _lod2Action = new QAction(tr("LOD 2"), this); + _lod2Action->setCheckable(true); + _externIDAction = new QAction(tr("extern ID"), this); + + connect(_setActivePSAction, SIGNAL(triggered()), this, SLOT(setActiveNode())); + connect(_savePSAction, SIGNAL(triggered()), this, SLOT(savePS())); + connect(_saveAsPSAction, SIGNAL(triggered()), this, SLOT(saveAsPS())); + connect(_clearContentAction, SIGNAL(triggered()), this, SLOT(clearContent())); + connect(_removeFromWSAction, SIGNAL(triggered()), this, SLOT(removePS())); + connect(_mergeAction, SIGNAL(triggered()), this, SLOT(mergePS())); + connect(_newLocatedAction, SIGNAL(triggered()), this, SLOT(newLocated())); + connect(_pasteLocatedAction, SIGNAL(triggered()), this, SLOT(pasteLocated())); + + connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(bindNewLocatedBindable(int))); + for(int i = 0; LocatedBindable[i]; ++i) + { + _signalMapper->setMapping(_bindNewLocatedBindable[i], i); + connect(_bindNewLocatedBindable[i], SIGNAL(triggered()), _signalMapper, SLOT(map())); + } + + connect(_forceZBiasAction, SIGNAL(triggered()), this, SLOT(forceZBias())); + + connect(_copyLocatedAction, SIGNAL(triggered()), this, SLOT(copyLocated())); + connect(_copyBindableAction, SIGNAL(triggered()), this, SLOT(copyBindable())); + connect(_pasteBindableAction, SIGNAL(triggered()), this, SLOT(pasteBindable())); + connect(_deleteAction, SIGNAL(triggered()), this, SLOT(deleteItem())); + + connect(_instanciateAction, SIGNAL(triggered()), this, SLOT(setInstanciate())); + connect(_allLODAction, SIGNAL(triggered()), this, SLOT(setAllLOD())); + connect(_lod1Action, SIGNAL(triggered()), this, SLOT(setLOD1())); + connect(_lod2Action, SIGNAL(triggered()), this, SLOT(setLOD2())); + connect(_externIDAction, SIGNAL(triggered()), this, SLOT(setExternID())); +} + +CParticleWorkspaceDialog::~CParticleWorkspaceDialog() +{ +} + +void CParticleWorkspaceDialog::touchPSState(CParticleTreeItem *item) +{ + if (item == NULL) return; + CWorkspaceNode *ownerNode = _treeModel->getOwnerNode(item); + if (ownerNode && ownerNode->getPSModel()) + { + ownerNode->getPSModel()->touchLightableState(); + ownerNode->getPSModel()->touchTransparencyState(); + } +} + +void CParticleWorkspaceDialog::clickedItem(const QModelIndex & index) +{ + if (_currentItem != NULL) + _treeModel->getOwnerNode(_currentItem)->getPSPointer()->setCurrentEditedElement(NULL); + + _currentItem = static_cast(index.internalPointer()); + + if (index.flags() != Qt::NoItemFlags) + _PropertyDialog->setCurrentEditedElement(_currentItem); + + if ((_currentItem->itemType() == ItemType::Workspace) || + (_currentItem->itemType() == ItemType::ParticleSystemNotLoaded)) + _currentItem = NULL; +} + +void CParticleWorkspaceDialog::customContextMenu() +{ + if (!Modules::psEdit().getParticleWorkspace()) return; + clickedItem(_ui.treeView->currentIndex()); + if (_currentItem == NULL) return; + QMenu *popurMenu = new QMenu(this); + switch (_currentItem->itemType()) + { + case ItemType::ParticleSystem: + popurMenu->addAction(_setActivePSAction); + popurMenu->addAction(_savePSAction); + popurMenu->addAction(_saveAsPSAction); + popurMenu->addAction(_clearContentAction); + popurMenu->addAction(_removeFromWSAction); + popurMenu->addAction(_mergeAction); + popurMenu->addSeparator(); + popurMenu->addAction(_newLocatedAction); + popurMenu->addAction(_pasteLocatedAction); + popurMenu->addSeparator(); + buildMenu(popurMenu); + popurMenu->addSeparator(); + popurMenu->addAction(_forceZBiasAction); + break; + case ItemType::Located: + popurMenu->addAction(_instanciateAction); + popurMenu->addSeparator(); + buildMenu(popurMenu); + popurMenu->addSeparator(); + popurMenu->addAction(_copyLocatedAction); + popurMenu->addAction(_pasteBindableAction); + popurMenu->addAction(_deleteAction); + break; + case ItemType::Force: + case ItemType::Particle: + case ItemType::Emitter: + case ItemType::Light: + case ItemType::CollisionZone: + case ItemType::Sound: + popurMenu->addAction(_copyBindableAction); + popurMenu->addAction(_deleteAction); + popurMenu->addSeparator(); + popurMenu->addAction(_allLODAction); + popurMenu->addAction(_lod1Action); + popurMenu->addAction(_lod2Action); + popurMenu->addSeparator(); + popurMenu->addAction(_externIDAction); + + // check the menu to tell which lod is used for this located bindable + if (_currentItem->getBind()->getLOD() == NL3D::PSLod1n2) _allLODAction->setChecked(true); + else _allLODAction->setChecked(false); + if (_currentItem->getBind()->getLOD() == NL3D::PSLod1) _lod1Action->setChecked(true); + else _lod1Action->setChecked(false); + if (_currentItem->getBind()->getLOD() == NL3D::PSLod2) _lod2Action->setChecked(true); + else _lod2Action->setChecked(false); + break; + case ItemType::LocatedInstance: + popurMenu->addAction(_deleteAction); + break; + } + popurMenu->exec(QCursor::pos()); + delete popurMenu; +} + +void CParticleWorkspaceDialog::setActiveNode() +{ + QModelIndex index = _ui.treeView->currentIndex(); + CParticleTreeItem *item = static_cast(index.internalPointer()); + nlassert(item->getNode()); + Modules::psEdit().setActiveNode(item->getNode()); + Q_EMIT changeActiveNode(); +} + +void CParticleWorkspaceDialog::savePS() +{ + _currentItem->getNode()->savePS(); + _currentItem->getNode()->setModified(false); +} + +void CParticleWorkspaceDialog::saveAsPS() +{ + CWorkspaceNode *node = _treeModel->getOwnerNode(_currentItem); + //if (nt->PS->getResetAutoCountFlag() && nt->PS->getPSPointer()->getAutoCountFlag()) + if (node->getPSPointer()->getAutoCountFlag()) + { + QMessageBox::critical(this, tr("NeL particle system editor"), + QString(node->getFilename().c_str()) + tr(" uses auto count feature, and it has been modified. " + "You should run the system entirely at least once at full detail before saving so that the editor can compute the number of particles in the system. " + "If user params are used to modify system aspect, you should run the system for extreme cases before saving. "), + QMessageBox::Ok); + } + else + { + Modules::psEdit().stop(); + + QString fileName = QFileDialog::getSaveFileName(this, tr("Save as ps file"), + ".", + tr("ps files (*.ps)")); + // after check + if (!fileName.isEmpty()) + node->savePSAs(fileName.toStdString()); + } +} + +void CParticleWorkspaceDialog::clearContent() +{ + int ret = QMessageBox::question(this, tr("NeL particle system editor"), + tr("Clear content ?"), QMessageBox::Yes | QMessageBox::No); + + if (ret == QMessageBox::Yes) + { + CWorkspaceNode *node = _treeModel->getOwnerNode(_currentItem); + nlassert(node); + node->setResetAutoCountFlag(false); + while (_currentItem->childCount() != 0) + _treeModel->removeRows(0, _ui.treeView->currentIndex()); + node->createEmptyPS(); + node->setModified(true); + } +} + +void CParticleWorkspaceDialog::removePS() +{ + if (_treeModel->getOwnerNode(_currentItem) == Modules::psEdit().getActiveNode()) + Modules::psEdit().setActiveNode(NULL); + + QModelIndex index = _ui.treeView->currentIndex(); + _ui.treeView->setCurrentIndex(index.parent()); + clickedItem(index.parent()); + Modules::psEdit().getParticleWorkspace()->removeNode(static_cast(index.internalPointer())->getNode()); + _treeModel->removeRows(index.row(), index.parent()); +} + +void CParticleWorkspaceDialog::mergePS() +{ +} + +void CParticleWorkspaceDialog::newLocated() +{ + _treeModel->getOwnerNode(_currentItem)->setModified(true); + createLocated(_treeModel->getOwnerNode(_currentItem)->getPSPointer()); +} + +void CParticleWorkspaceDialog::pasteLocated() +{ + nlassert(_currentItem->getNode()); + _treeModel->getOwnerNode(_currentItem)->setModified(true); + + Modules::psEdit().resetAutoCount(_treeModel->getOwnerNode(_currentItem)); + + NL3D::CPSLocated *copy = dynamic_cast(::DupPSLocated(_LocatedCopy.get())); + if (!copy) return; + if (_currentItem->getNode()->getPSPointer()->attach(copy)) + _treeModel->insertRows(copy, _currentItem->childCount(), _ui.treeView->currentIndex()); + else + { + delete copy; + QMessageBox::critical(this, tr("NeL particle system editor"), + tr("Can't perform operation : the system is flagged with 'No max nb steps' or uses the preset 'Spell FX', " + "and thus, should have a finite duration. Please remove that flag first."), + QMessageBox::Ok); + } +} + +void CParticleWorkspaceDialog::bindNewLocatedBindable(int id) +{ + NL3D::CPSLocatedBindable *toCreate = NULL; + switch (id) + { + case Action::ParticlePoint: + toCreate = new NL3D::CPSDot; + break; + case Action::ParticleLookAt: + toCreate = new NL3D::CPSFaceLookAt; + break; + case Action::ParticleFanLight: + toCreate = new NL3D::CPSFanLight; + break; + case Action::ParticleRibbon: + toCreate = new NL3D::CPSRibbon; + break; + case Action::ParticleTailDot: + toCreate = new NL3D::CPSTailDot; + break; + case Action::ParticleMesh: + toCreate = new NL3D::CPSMesh; + break; + case Action::ParticleConstraintMesh: + toCreate = new NL3D::CPSConstraintMesh; + break; + case Action::ParticleFace: + toCreate = new NL3D::CPSFace; + break; + case Action::ParticleShockWave: + toCreate = new NL3D::CPSShockWave; + break; + case Action::ParticleRibbonLookAt: + toCreate = new NL3D::CPSRibbonLookAt; + break; + case Action::ForceGravity: + toCreate = new NL3D::CPSGravity; + break; + case Action::ForceDirectional: + toCreate = new NL3D::CPSDirectionnalForce; + break; + case Action::ForceSpring: + toCreate = new NL3D::CPSSpring; + break; + case Action::ForceFlyidFriction: + toCreate = new NL3D::CPSFluidFriction; + break; + case Action::ForceCentralGravity: + toCreate = new NL3D::CPSCentralGravity; + break; + case Action::ForceCylindricVortex: + toCreate = new NL3D::CPSCylindricVortex; + break; + case Action::ForceBrownianMove: + toCreate = new NL3D::CPSBrownianForce; + break; + case Action::ForceMagnetic: + toCreate = new NL3D::CPSMagneticForce; + break; + case Action::ZonePlane: + toCreate = new NL3D::CPSZonePlane; + break; + case Action::ZoneSphere: + toCreate = new NL3D::CPSZoneSphere; + break; + case Action::ZoneRectangle: + toCreate = new NL3D::CPSZoneRectangle; + break; + case Action::ZoneDisc: + toCreate = new NL3D::CPSZoneDisc; + break; + case Action::ZoneCylinder: + toCreate = new NL3D::CPSZoneCylinder; + break; + case Action::EmitterDirectional: + toCreate = new NL3D::CPSEmitterDirectionnal; + break; + case Action::EmitterOmniDirectional: + toCreate = new NL3D::CPSEmitterOmni; + break; + case Action::EmitterRectangle: + toCreate = new NL3D::CPSEmitterRectangle; + break; + case Action::EmitterConic: + toCreate = new NL3D::CPSEmitterConic; + break; + case Action::EmitterSpherical: + toCreate = new NL3D::CPSSphericalEmitter; + break; + case Action::EmitterRadial: + toCreate = new NL3D::CPSRadialEmitter; + break; + case Action::Sound: + toCreate = new NL3D::CPSSound; + if (!Modules::psEdit().isRunning()) + (static_cast(toCreate))->stopSound(); + break; + case Action::Light: + toCreate = new NL3D::CPSLight; + break; + } + + _treeModel->getOwnerNode(_currentItem)->setModified(true); + + NL3D::CPSLocated *loc; + + if (_currentItem->itemType() == ItemType::ParticleSystem) + { + loc = createLocated(_treeModel->getOwnerNode(_currentItem)->getPSPointer()); + if (_treeModel->getOwnerNode(_currentItem)->getPSPointer()->getBypassMaxNumIntegrationSteps()) + { + if (toCreate->getType() == NL3D::PSParticle || toCreate->getType() == NL3D::PSEmitter) + loc->setInitialLife(1.f); + // object must have finite duration with that flag + } + } + else + loc = _currentItem->getLoc(); + + if (!loc->bind(toCreate)) + { + QMessageBox::critical(this, tr("NeL particle system editor"), + tr("The system is flagged with 'No max Nb steps', or uses the preset 'Spell FX'." + "System must have finite duration. Can't add object. To solve this, set a limited life time for the father."), + QMessageBox::Ok); + delete toCreate; + return; + } + + // complete the name + std::string name = toCreate->getName(); + if (_PSElementIdentifiers.count(name)) + { + name += (QString("%1").arg(++_PSElementIdentifiers[name])).toStdString(); + toCreate->setName(name); + } + else + { + _PSElementIdentifiers[toCreate->getName()] = 0; + toCreate->setName(name + "0"); + } + + touchPSState(_currentItem); + + Modules::psEdit().resetAutoCount(_treeModel->getOwnerNode(_currentItem)); + + // update treeView + if (_currentItem->itemType() == ItemType::ParticleSystem) + { + QModelIndex index = _treeModel->index(_currentItem->childCount() - 1, 0, _ui.treeView->currentIndex()); + _treeModel->insertRow(toCreate, 0, index); + } + else + _treeModel->insertRow(toCreate, _currentItem->childCount(), _ui.treeView->currentIndex()); +} + +void CParticleWorkspaceDialog::forceZBias() +{ + bool ok; + double d = QInputDialog::getDouble(this, tr("All object force ZBias"), tr(""), 0.0, -999999.0, 999999.0, 2, &ok); + if (!ok) return; + + nlassert(_treeModel->getOwnerNode(_currentItem)->getPSPointer()); + + _treeModel->getOwnerNode(_currentItem)->getPSPointer()->setZBias(-float(d)); + _treeModel->getOwnerNode(_currentItem)->setModified(true); +} + +void CParticleWorkspaceDialog::copyLocated() +{ + nlassert(_currentItem->getLoc()); + _LocatedCopy.reset(NLMISC::safe_cast(::DupPSLocated(_currentItem->getLoc()))); +} + +void CParticleWorkspaceDialog::copyBindable() +{ + nlassert(_currentItem->getBind()); + _LocatedBindableCopy.reset(::DupPSLocatedBindable(_currentItem->getBind())); +} + +void CParticleWorkspaceDialog::pasteBindable() +{ + nlassert(_currentItem->getLoc()); + _treeModel->getOwnerNode(_currentItem)->setModified(true); + + Modules::psEdit().resetAutoCount(_treeModel->getOwnerNode(_currentItem)); + + NL3D::CPSLocatedBindable *copy = ::DupPSLocatedBindable(_LocatedBindableCopy.get()); + if (!copy) return; + if (_currentItem->getLoc()->bind(copy)) + _treeModel->insertRow(copy, _currentItem->childCount(), _ui.treeView->currentIndex()); + else + { + delete copy; + QMessageBox::critical(this, tr("NeL particle system editor"), + tr("Can't perform operation : the system is flagged with 'No max nb steps' or uses the preset 'Spell FX', " + "and thus, should have a finite duration. Please remove that flag first."), + QMessageBox::Ok); + } +} + +void CParticleWorkspaceDialog::deleteItem() +{ + _treeModel->getOwnerNode(_currentItem)->setModified(true); + QModelIndex index = _ui.treeView->currentIndex(); + CParticleTreeItem *item = static_cast(index.internalPointer()); + CWorkspaceNode *ownerNode = _treeModel->getOwnerNode(item); + nlassert(ownerNode); + _ui.treeView->setCurrentIndex(index.parent()); + clickedItem(index.parent()); + switch(item->itemType()) + { + case ItemType::Located: + { + NL3D::CPSLocated *loc = item->getLoc(); + touchPSState(item); + ownerNode->setModified(true); + // if the system is running, we must destroy initial infos about the located, + // as they won't need to be restored when the stop button will be pressed + ownerNode->removeLocated(loc); + + Modules::psEdit().resetAutoCount(ownerNode); + + ownerNode->getPSPointer()->remove(loc); + _treeModel->removeRows(index.row(), index.parent()); + } + break; + case ItemType::LocatedInstance: + { + Modules::psEdit().resetAutoCount(ownerNode); + NL3D::CPSEmitter::setBypassEmitOnDeath(true); + item->getLoc()->deleteElement(item->getLocatedInstanceIndex()); + NL3D::CPSEmitter::setBypassEmitOnDeath(false); + _treeModel->removeRows(index.row(), index.parent()); + _treeModel->rebuildLocatedInstance(_ui.treeView->currentIndex()); + } + break; + case ItemType::Particle: + case ItemType::Emitter: + case ItemType::Force: + case ItemType::Light: + case ItemType::Sound: + case ItemType::CollisionZone: + { + NL3D::CPSLocatedBindable *lb = item->getBind(); + touchPSState(item); + // if the system is running, we must destroy initial infos + // that what saved about the located bindable, when the start button was pressed, as they won't need + // to be restored + ownerNode->removeLocatedBindable(lb); + ownerNode->setModified(true); + Modules::psEdit().resetAutoCount(ownerNode); + lb->getOwner()->remove(lb); + _treeModel->removeRows(index.row(), index.parent()); + } + break; + } +} + +void CParticleWorkspaceDialog::setInstanciate() +{ + _treeModel->getOwnerNode(_currentItem)->setModified(true); + + Modules::psEdit().resetAutoCount(_treeModel->getOwnerNode(_currentItem)); + + if (_currentItem->getLoc()->getSize() == _currentItem->getLoc()->getMaxSize()) + _currentItem->getLoc()->resize(_currentItem->getLoc()->getMaxSize() + 1); + + sint32 objIndex = _currentItem->getLoc()->newElement(NLMISC::CVector::Null, NLMISC::CVector::Null, + NULL, 0, _currentItem->getLoc()->getMatrixMode(), 0.f); + + _treeModel->insertRow(_currentItem->getLoc(), objIndex, _currentItem->childCount(), _ui.treeView->currentIndex()); +} + +void CParticleWorkspaceDialog::setAllLOD() +{ + _currentItem->getBind()->setLOD(NL3D::PSLod1n2); + _treeModel->getOwnerNode(_currentItem)->setModified(true); +} + +void CParticleWorkspaceDialog::setLOD1() +{ + _currentItem->getBind()->setLOD(NL3D::PSLod1); + _treeModel->getOwnerNode(_currentItem)->setModified(true); +} + +void CParticleWorkspaceDialog::setLOD2() +{ + _currentItem->getBind()->setLOD(NL3D::PSLod2); + _treeModel->getOwnerNode(_currentItem)->setModified(true); +} + +void CParticleWorkspaceDialog::setExternID() +{ + bool ok; + int i = QInputDialog::getInt(this, tr("Set the extern ID"), + tr("0 means no extern access."), + _currentItem->getBind()->getExternID(), 0, 9999, 1, &ok); + if (ok) + { + _currentItem->getBind()->setExternID(uint32(i)); + _treeModel->getOwnerNode(_currentItem)->setModified(true); + } +} + +void CParticleWorkspaceDialog::setNewState() +{ + if ((_currentItem != NULL) && (_currentItem->itemType() == ItemType::LocatedInstance)) + { + QModelIndex index = _ui.treeView->currentIndex(); + _ui.treeView->setCurrentIndex(_ui.treeView->currentIndex().parent()); + clickedItem(index.parent()); + } + _ui.treeView->update(); + _ui.treeView->repaint(); +} + +void CParticleWorkspaceDialog::updateTreeView() +{ + _ui.treeView->update(); + _ui.treeView->repaint(); +} + +void CParticleWorkspaceDialog::buildMenu(QMenu *menu) +{ + QMenu *bindParticleMenu = new QMenu(tr("Bind particle..."), menu); + menu->addAction(bindParticleMenu->menuAction()); + for(int i = Action::ParticlePoint; i <= Action::ParticleRibbonLookAt; ++i) + bindParticleMenu->addAction(_bindNewLocatedBindable[i]); + + QMenu *bindForceMenu = new QMenu(tr("Bind force..."), menu); + menu->addAction(bindForceMenu->menuAction()); + for(int i = Action::ForceGravity; i <= Action::ForceMagnetic; ++i) + bindForceMenu->addAction(_bindNewLocatedBindable[i]); + + QMenu *bindZoneMenu = new QMenu(tr("Bind zone..."), menu); + menu->addAction(bindZoneMenu->menuAction()); + for(int i = Action::ZonePlane; i <= Action::ZoneCylinder; ++i) + bindZoneMenu->addAction(_bindNewLocatedBindable[i]); + + QMenu *bindEmitterMenu = new QMenu(tr("Bind emitter..."), menu); + menu->addAction(bindEmitterMenu->menuAction()); + for(int i = Action::EmitterDirectional; i <= Action::EmitterRadial; ++i) + bindEmitterMenu->addAction(_bindNewLocatedBindable[i]); + + menu->addAction(_bindNewLocatedBindable[Action::Sound]); + menu->addAction(_bindNewLocatedBindable[Action::Light]); +} + +NL3D::CPSLocated *CParticleWorkspaceDialog::createLocated(NL3D::CParticleSystem *ps) +{ + // build new name + std::string name; + if (_PSElementIdentifiers.count(std::string("located"))) + name = (QString("located %1").arg(++_PSElementIdentifiers[std::string("located")])).toStdString(); + + else + { + name = std::string("located 0"); + _PSElementIdentifiers["located"] = 0; + } + NL3D::CPSLocated *loc = new NL3D::CPSLocated; + + loc->setName(name); + loc->setMatrixMode(NL3D::PSFXWorldMatrix); + ps->attach(loc); + + touchPSState(_currentItem); + + // update treeView + _treeModel->insertRows(loc, _currentItem->childCount(), _ui.treeView->currentIndex()); + + return loc; +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.h new file mode 100644 index 000000000..825f9f05b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_dialog.h @@ -0,0 +1,133 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_WORKSPACE_DIALOG_H +#define PARTICLE_WORKSPACE_DIALOG_H + +#include +#include "ui_particle_workspace_form.h" + +// Qt includes +#include + +// NeL includes +#include "nel/misc/smart_ptr.h" + +// Projects includes +#include "particle_property_dialog.h" +#include "particle_tree_model.h" +#include "particle_node.h" + +namespace NLQT { + +/** +@class ParticleWorkspaceDialog +@brief Displays particles workspace in QTreeView, build popur menu (operations with particles system sub-items). +*/ +class CParticleWorkspaceDialog: public QDockWidget +{ + Q_OBJECT +public: + CParticleWorkspaceDialog(QWidget *parent = 0); + ~CParticleWorkspaceDialog(); + + void touchPSState(CParticleTreeItem *item); + CPropertyDialog *getPropertyDialog() const { return _PropertyDialog; } + +Q_SIGNALS: + /// Emits change active particle system node. + void changeActiveNode(); + +private Q_SLOTS: + void clickedItem(const QModelIndex & index); + void customContextMenu(); + + void setActiveNode(); + void savePS(); + void saveAsPS(); + void clearContent(); + void removePS(); + void mergePS(); + + void newLocated(); + void pasteLocated(); + + void bindNewLocatedBindable(int id); + + void forceZBias(); + + void copyLocated(); + void copyBindable(); + void pasteBindable(); + void deleteItem(); + + void setInstanciate(); + void setAllLOD(); + void setLOD1(); + void setLOD2(); + + void setExternID(); + + void setNewState(); + + void updateTreeView(); + +private: + void buildMenu(QMenu *menu); + NL3D::CPSLocated *createLocated(NL3D::CParticleSystem *ps); + + CPropertyDialog *_PropertyDialog; + CParticleTreeModel *_treeModel; + + QSignalMapper *_signalMapper; + + QAction *_setActivePSAction; + QAction *_savePSAction; + QAction *_saveAsPSAction; + QAction *_clearContentAction; + QAction *_removeFromWSAction; + QAction *_mergeAction; + QAction *_newLocatedAction; + QAction *_pasteLocatedAction; + QAction *_bindNewLocatedBindable[32]; + QAction *_forceZBiasAction; + + QAction *_instanciateAction; + QAction *_copyLocatedAction; + QAction *_copyBindableAction; + QAction *_pasteBindableAction; + QAction *_deleteAction; + + QAction *_allLODAction; + QAction *_lod1Action; + QAction *_lod2Action; + QAction *_externIDAction; + + std::auto_ptr _LocatedCopy; + std::auto_ptr _LocatedBindableCopy; + + CParticleTreeItem *_currentItem; + + Ui::CParticleWorkspaceDialog _ui; + friend class CMainWindow; +}; /* class CParticleWorkspaceDialog */ + +} /* namespace NLQT */ + +#endif // PARTICLE_WORKSPACE_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_form.ui new file mode 100644 index 000000000..d44d74c31 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_form.ui @@ -0,0 +1,36 @@ + + + CParticleWorkspaceDialog + + + + 0 + 0 + 270 + 293 + + + + Tree workspace + + + + + + + true + + + false + + + false + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.cpp new file mode 100644 index 000000000..211a9712f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.cpp @@ -0,0 +1,197 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_workspace_page.h" + +// Qt includes +#include +#include +#include + +// NeL includes + +// Project includes +#include "modules.h" + +namespace NLQT { + +CWorkspacePage::CWorkspacePage(CParticleTreeModel *treeModel, QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _treeModel = treeModel; + + connect(_ui.newToolButton, SIGNAL(clicked()), this, SLOT(newWP())); + connect(_ui.loadToolButton, SIGNAL(clicked()), this, SLOT(loadWP())); + connect(_ui.saveToolButton, SIGNAL(clicked()), this, SLOT(saveWP())); + connect(_ui.saveAsToolButton, SIGNAL(clicked()), this, SLOT(saveAsWP())); + connect(_ui.insertToolButton, SIGNAL(clicked()), this, SLOT(insertPS())); + connect(_ui.createToolButton, SIGNAL(clicked()), this, SLOT(createPS())); + connect(_ui.resetToolButton, SIGNAL(clicked()), this, SLOT(removeAllPS())); +} + +CWorkspacePage::~CWorkspacePage() +{ +} + +void CWorkspacePage::newWP() +{ + //checkModifiedWorkSpace(); + // ask name of the new workspace to create + QString fileName = QFileDialog::getSaveFileName(this, tr("Create new pws file"), + ".", + tr("pws files (*.pws)")); + if (!fileName.isEmpty()) + { + Modules::psEdit().createNewWorkspace(fileName.toStdString()); + _treeModel->setupModelFromWorkSpace(); + _ui.saveToolButton->setEnabled(true); + _ui.saveAsToolButton->setEnabled(true); + _ui.insertToolButton->setEnabled(true); + _ui.resetToolButton->setEnabled(true); + _ui.createToolButton->setEnabled(true); + } +} + +void CWorkspacePage::loadWP() +{ + //checkModifiedWorkSpace(); + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open NeL data file"), ".", + tr("Particle Workspace file (*.pws);;")); + + setCursor(Qt::WaitCursor); + if (!fileName.isEmpty()) + { + Modules::psEdit().loadWorkspace(fileName.toStdString()); + _treeModel->setupModelFromWorkSpace(); + _ui.saveToolButton->setEnabled(true); + _ui.saveAsToolButton->setEnabled(true); + _ui.insertToolButton->setEnabled(true); + _ui.resetToolButton->setEnabled(true); + _ui.createToolButton->setEnabled(true); + } + setCursor(Qt::ArrowCursor); +} + +void CWorkspacePage::saveWP() +{ + //checkModifiedWorkSpace(); + Modules::psEdit().saveWorkspaceStructure(); + Modules::psEdit().saveWorkspaceContent(); +} + +void CWorkspacePage::saveAsWP() +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Save as pws file"), + ".", + tr("pws files (*.pws)")); + if (!fileName.isEmpty()) + { + Modules::psEdit().getParticleWorkspace()->setFileName(fileName.toStdString()); + Modules::psEdit().saveWorkspaceStructure(); + Modules::psEdit().saveWorkspaceContent(); + _treeModel->setupModelFromWorkSpace(); + } +} + +void CWorkspacePage::insertPS() +{ + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open NeL data file"), ".", + tr("Particle System file (*.ps);;")); + + if (!fileName.isEmpty()) + { + // TODO: create method particle editor insertNewPS and multiple add + CWorkspaceNode *node = Modules::psEdit().getParticleWorkspace()->addNode(NLMISC::CFile::getFilename(fileName.toStdString())); + if (node != NULL) + { + try + { + node->loadPS(); + } + catch(NLMISC::EStream &e) + { + QMessageBox::critical(this, tr("NeL particle system editor"), + QString(e.what()), + QMessageBox::Ok); + } + if (!node->isLoaded()) + Modules::psEdit().getParticleWorkspace()->removeNode(Modules::psEdit().getParticleWorkspace()->getNumNode() - 1); + else + { + QModelIndex index = _treeModel->index(0, 0); + _treeModel->insertRows(node, static_cast(index.internalPointer())->childCount(), index); + } + } + } +} + +void CWorkspacePage::createPS() +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Create new particle system file"), + ".", + tr("ps files (*.ps)")); + if (!fileName.isEmpty()) + { + + // TODO: create method particle editor createNewPS + if (Modules::psEdit().getParticleWorkspace()->containsFile(NLMISC::CFile::getFilename(fileName.toStdString()))) + { + QMessageBox::critical(this, tr("NeL particle system editor"), + tr("Failed to create new particle system"), + QMessageBox::Ok); + return; + } + CWorkspaceNode *node = Modules::psEdit().getParticleWorkspace()->addNode(NLMISC::CFile::getFilename(fileName.toStdString())); + // should always succeed because we tested if file already exists + nlassert(node); + node->createEmptyPS(); + try + { + node->savePS(); + node->setModified(false); + } + catch (NLMISC::Exception &e) + { + QMessageBox::critical(this, tr("NeL particle system editor"), + QString(e.what()), + QMessageBox::Ok); + return; + } + QModelIndex index = _treeModel->index(0, 0); + _treeModel->insertRows(node, static_cast(index.internalPointer())->childCount(), index); + } +} + +void CWorkspacePage::removeAllPS() +{ + Modules::psEdit().setActiveNode(NULL); + // TODO: create method particle editor clearParticleWorkspace + uint numNodes = Modules::psEdit().getParticleWorkspace()->getNumNode(); + for(uint k = 0; k < numNodes; ++k) + Modules::psEdit().getParticleWorkspace()->removeNode((uint) 0); + + _treeModel->setupModelFromWorkSpace(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.h b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.h new file mode 100644 index 000000000..29504e490 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_workspace_page.h @@ -0,0 +1,68 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_WORKSPACE_PAGE_H +#define PARTICLE_WORKSPACE_PAGE_H + +#include +#include "ui_workspace_form.h" + +// STL includes + +// NeL includes +#include + +// Project includes +#include "particle_tree_model.h" + +namespace NLQT { + +/** +@class CWorkspacePage +@brief Page for QStackWidget, to particles workspace operation (new/load/save workspace, create/insert/remove all particles system to workspace) +*/ +class CWorkspacePage: public QWidget +{ + Q_OBJECT + +public: + CWorkspacePage(CParticleTreeModel *treeModel, QWidget *parent = 0); + ~CWorkspacePage(); + +private Q_SLOTS: + void newWP(); + void loadWP(); + void saveWP(); + void saveAsWP(); + void insertPS(); + void createPS(); + void removeAllPS(); + +private: + + Ui::CWorkspacePage _ui; + + CParticleTreeModel *_treeModel; + friend class CPropertyDialog; + friend class CParticleWorkspaceDialog; +}; /* class CWorkspacePage */ + +} /* namespace NLQT */ + +#endif // PARTICLE_WORKSPACE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_zone_form.ui b/code/nel/tools/3d/object_viewer_qt/src/particle_zone_form.ui new file mode 100644 index 000000000..09a2b8540 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_zone_form.ui @@ -0,0 +1,151 @@ + + + CZonePage + + + + 0 + 0 + 246 + 350 + + + + Form + + + + + + Targets: + + + + + + + Avaible targets: + + + + + + + + + + Qt::Vertical + + + + 20 + 58 + + + + + + + + + + + + 30 + 16777215 + + + + < + + + + + + + + 30 + 16777215 + + + + > + + + + + + + Qt::Vertical + + + + 20 + 84 + + + + + + + + + Bounce + + + + + Destroy + + + + + + + + Qt::Horizontal + + + + 78 + 20 + + + + + + + + Bounce factor: + + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.cpp new file mode 100644 index 000000000..0a1187602 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.cpp @@ -0,0 +1,151 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "particle_zone_page.h" + +// Qt includes + +// NeL includes + +// Project includes +#include "particle_force_page.h" +#include "modules.h" + +namespace NLQT { + +CZonePage::CZonePage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.bounceFactorWidget->setRange(0.f, 1.f); + _ui.bounceFactorWidget->setWrapper(&_BounceFactorWrapper); + + connect(_ui.toTargetsPushButton, SIGNAL(clicked()), this, SLOT(addTarget())); + connect(_ui.toAvaibleTargetsPushButton, SIGNAL(clicked()), this, SLOT(removeTarget())); + connect(_ui.bounceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setBounce(int))); +} + +CZonePage::~CZonePage() +{ +} + +void CZonePage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable) +{ + _Node = ownerNode; + _LBTarget = static_cast(locatedBindable); + _Zone = dynamic_cast(_LBTarget); + + updateTargets(); + + _BounceFactorWrapper.OwnerNode = _Node; + _BounceFactorWrapper.Z = _Zone; + + _ui.bounceFactorWidget->updateUi(); + + _ui.bounceComboBox->setCurrentIndex( _Zone->getCollisionBehaviour()); +} + +void CZonePage::addTarget() +{ + // TODO: multiple add items + int totalCount = _ui.avaibleTargetsListWidget->count(); + if ((totalCount == 0) || (_ui.avaibleTargetsListWidget->currentRow() == -1)) return; + + CLocatedItem *item = dynamic_cast(_ui.avaibleTargetsListWidget->currentItem()); + + NL3D::CPSLocated *loc = item->getUserData(); + nlassert(loc); + + _LBTarget->attachTarget(loc); + + _ui.avaibleTargetsListWidget->takeItem(_ui.avaibleTargetsListWidget->currentRow()); + _ui.targetsListWidget->addItem(item); + + updateModifiedFlag(); +} + +void CZonePage::removeTarget() +{ + // TODO: multiple remove items + int totalCount = _ui.targetsListWidget->count(); + if ((totalCount == 0) || (_ui.targetsListWidget->currentRow() == -1)) return; + + CLocatedItem *item = dynamic_cast(_ui.targetsListWidget->takeItem(_ui.targetsListWidget->currentRow())); + + NL3D::CPSLocated *loc = item->getUserData(); + nlassert(loc); + + _LBTarget->detachTarget(loc); + + _ui.avaibleTargetsListWidget->addItem(item); + updateModifiedFlag(); +} + +void CZonePage::setBounce(int index) +{ + if (_Zone->getCollisionBehaviour() != index) + _Zone->setCollisionBehaviour( (NL3D::CPSZone::TCollisionBehaviour) index); + + _ui.bounceFactorWidget->setEnabled(_Zone->getCollisionBehaviour() == NL3D::CPSZone::bounce ? true : false); + Modules::psEdit().resetAutoCount(_Node); +} + +void CZonePage::updateTargets() +{ + uint k; + uint nbTarg = _LBTarget->getNbTargets(); + + _ui.targetsListWidget->clear(); + + std::set targetSet; + + // fill the box thta tells us what the target are + for(k = 0; k < nbTarg; ++k) + { + CLocatedItem *item = new CLocatedItem(QString(_LBTarget->getTarget(k)->getName().c_str()), + _ui.targetsListWidget); + item->setUserData(_LBTarget->getTarget(k)); + targetSet.insert(_LBTarget->getTarget(k)); + }; + + // fill abox with the available targets + NL3D::CParticleSystem *ps = _LBTarget->getOwner()->getOwner(); + + uint nbLocated = ps->getNbProcess(); + + _ui.avaibleTargetsListWidget->clear(); + + for (k = 0; k < nbLocated; ++k) + { + NL3D::CPSLocated *loc = dynamic_cast(ps->getProcess(k)); + if (loc) + { + if (targetSet.find(loc) == targetSet.end()) + { + CLocatedItem *item = new CLocatedItem(QString(loc->getName().c_str()), + _ui.avaibleTargetsListWidget); + item->setUserData(loc); + } + } + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.h b/code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.h new file mode 100644 index 000000000..ffbc0c32a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/particle_zone_page.h @@ -0,0 +1,80 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PARTICLE_ZONE_PAGE_H +#define PARTICLE_ZONE_PAGE_H + +#include +#include "ui_particle_zone_form.h" + +// STL includes + +// NeL includes +#include "nel/3d/ps_zone.h" + +// Project includes +#include "particle_node.h" + +namespace NLQT { + +class CZonePage: public QWidget +{ + Q_OBJECT + +public: + CZonePage(QWidget *parent = 0); + virtual ~CZonePage(); + + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocatedBindable *locatedBindable); + +private Q_SLOTS: + void addTarget(); + void removeTarget(); + void setBounce(int index); + +private: + + /// wrapper to set the bounce factor of a collision zone + struct CBounceFactorWrapper : public IPSWrapperFloat + { + // the zone being wrapped + NL3D::CPSZone *Z ; + float get(void) const { return Z->getBounceFactor() ; } + void set(const float &v) { Z->setBounceFactor(v) ; } + } _BounceFactorWrapper ; + + void updateTargets(); + + void updateModifiedFlag() { if (_Node) _Node->setModified(true); } + + /// the target we're focusing on + NL3D::CPSTargetLocatedBindable *_LBTarget; + + /// the collision zone being edited + NL3D::CPSZone *_Zone ; + + CWorkspaceNode *_Node; + + Ui::CZonePage _ui; + +}; /* class CZonePage */ + +} /* namespace NLQT */ + +#endif // PARTICLE_ZONE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.cpp b/code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.cpp new file mode 100644 index 000000000..222cd498a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.cpp @@ -0,0 +1,209 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#include "stdpch.h" +#include "ps_initial_pos.h" + +#include "nel/3d/particle_system.h" +#include "nel/3d/ps_located.h" +#include "nel/3d/ps_edit.h" +#include "nel/3d/ps_emitter.h" + +namespace NLQT { + +void CPSInitialPos::reset() +{ + _InitInfoVect.clear(); + _RotScaleInfoVect.clear(); + _InitialSizeVect.clear(); +} + +void CPSInitialPos::copySystemInitialPos(NL3D::CParticleSystem *ps) +{ + nlassert(!_PS); + reset(); + uint32 nbLocated = ps->getNbProcess(); + + _PS = ps; + for(uint32 k = 0; k < nbLocated; ++k) + { + + NL3D::CPSLocated *loc = dynamic_cast(ps->getProcess(k)); + if (loc) + { + + _InitialSizeVect.push_back(std::make_pair(loc, loc->getSize()) ); + for (uint32 l = 0; l < loc->getSize(); ++l) + { + + CInitPSInstanceInfo ii; + ii.Index = l; + ii.Loc = loc; + ii.Pos = loc->getPos()[l]; + ii.Speed = loc->getSpeed()[l]; + _InitInfoVect.push_back(ii); + + for (uint32 m = 0; m < loc->getNbBoundObjects(); ++m) + { + + if (dynamic_cast(loc->getBoundObject(m))) + { + CRotScaleInfo rsi; + rsi.Loc = loc; + rsi.LB = loc->getBoundObject(m); + rsi.Index = l; + rsi.Psm = dynamic_cast(loc->getBoundObject(m)); + rsi.Scale = rsi.Psm->getScale(l); + rsi.Rot = rsi.Psm->getMatrix(l); + _RotScaleInfoVect.push_back(rsi); + } + } + } + } + } +} + + +// PRIVATE : a predicate used in CPSInitialPos::removeLocated +struct CRemoveLocatedPred +{ + NL3D::CPSLocated *Loc ; +}; + +// private : predicate to remove located from a CPSInitialPos::TInitialLocatedSizeVect vector +struct CRemoveLocatedFromLocatedSizePred : public CRemoveLocatedPred +{ + bool operator()(const std::pair &value) { return Loc == value.first; } +}; + +// private : predicate to remove located from a PSInitialPos::CInitPSInstanceInfo vector +struct CRemoveLocatedFromInitPSInstanceInfoVectPred : public CRemoveLocatedPred +{ + bool operator()(const CPSInitialPos::CInitPSInstanceInfo &value) { return value.Loc == Loc; } +}; + +// private : predicate to remove located from a PSInitialPos::CRotScaleInfo vector +struct CRemoveLocatedFromRotScaleInfoVectPred : public CRemoveLocatedPred +{ + bool operator()(const CPSInitialPos::CRotScaleInfo &value) { return value.Loc == Loc; } +}; + +// private : predicate to remove located bindable pointers in a TRotScaleInfoVect vect +struct CRemoveLocatedBindableFromRotScaleInfoVectPred +{ + // the located bindable taht has been removed + NL3D::CPSLocatedBindable *LB; + bool operator()(const CPSInitialPos::CRotScaleInfo &value) { return value.LB == LB; } +}; + +void CPSInitialPos::removeLocated(NL3D::CPSLocated *loc) +{ + // in each container, we delete every element that has a pointer over lthe located loc + // , by using the dedicated predicate. + + CRemoveLocatedFromLocatedSizePred p; + p.Loc = loc; + _InitialSizeVect.erase(std::remove_if(_InitialSizeVect.begin(), _InitialSizeVect.end(), p) + , _InitialSizeVect.end() ); + + CRemoveLocatedFromInitPSInstanceInfoVectPred p2; + p2.Loc = loc; + _InitInfoVect.erase(std::remove_if(_InitInfoVect.begin(), _InitInfoVect.end(), p2) + , _InitInfoVect.end()); + + CRemoveLocatedFromRotScaleInfoVectPred p3; + p3.Loc = loc; + _RotScaleInfoVect.erase(std::remove_if(_RotScaleInfoVect.begin(), _RotScaleInfoVect.end(), p3) + , _RotScaleInfoVect.end()); + +} + +void CPSInitialPos::removeLocatedBindable(NL3D::CPSLocatedBindable *lb) +{ + CRemoveLocatedBindableFromRotScaleInfoVectPred p; + p.LB = lb; + _RotScaleInfoVect.erase(std::remove_if(_RotScaleInfoVect.begin(), _RotScaleInfoVect.end(), p), _RotScaleInfoVect.end() ); +} + +// reinitialize the system with its initial instances positions +void CPSInitialPos::restoreSystem() +{ + nlassert(_PS); // no system has been memorized yet + _PS->stopSound(); + // delete all the instance of the system + NL3D::CPSEmitter::setBypassEmitOnDeath(true); + for (uint k = 0; k < _PS->getNbProcess(); ++k) + { + NL3D::CPSLocated *loc = dynamic_cast(_PS->getProcess(k)); + if (loc) + { + while (loc->getSize()) + { + loc->deleteElement(0); + } + + nlassert(loc->getSize() == 0); + } + } + // recreate the initial number of instances + for (TInitialLocatedSizeVect ::iterator itSize = _InitialSizeVect.begin(); itSize != _InitialSizeVect.end(); ++itSize) + { + // nlassert(itSize->first->getSize() == 0) + for (uint l = 0; l < itSize->second; ++l) + { + itSize->first->newElement(NLMISC::CVector::Null, NLMISC::CVector::Null, NULL, 0, itSize->first->getMatrixMode(), 0.f); + } + + uint realSize = itSize->first->getSize(); + uint size = itSize->second; + + } + NL3D::CPSEmitter::setBypassEmitOnDeath(false); + for (TInitInfoVect::iterator it = _InitInfoVect.begin(); it != _InitInfoVect.end(); ++it) + { + if (it->Index < it->Loc->getSize()) + { + it->Loc->getPos()[it->Index] = it->Pos; + it->Loc->getSpeed()[it->Index] = it->Speed; + // If the particle has parametric infos attach, restore them too + if (it->Loc->isParametricMotionEnabled()) + { + it->Loc->getParametricInfos()[it->Index].Pos = it->Pos; + it->Loc->getParametricInfos()[it->Index].Speed = it->Speed; + it->Loc->getParametricInfos()[it->Index].Date = 0.f; + } + } + } + for (TRotScaleInfoVect::iterator it2 = _RotScaleInfoVect.begin(); it2 != _RotScaleInfoVect.end(); ++it2) + { + if (it2->Index < it2->Loc->getSize()) + { + it2->Psm->setMatrix(it2->Index, it2->Rot); + if (it2->Psm->supportNonUniformScaling()) + { + it2->Psm->setScale(it2->Index, it2->Scale); + } + else if (it2->Psm->supportUniformScaling()) + { + it2->Psm->setScale(it2->Index, it2->Scale.x); + } + } + } + _PS = NULL; +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.h b/code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.h new file mode 100644 index 000000000..131305cbc --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/ps_initial_pos.h @@ -0,0 +1,101 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef NL_PS_INITIAL_POS +#define NL_PS_INITIAL_POS + +#include +//#include +#include +#include + +namespace NL3D +{ + class CParticleSystem; + class CPSLocated; + class CPSLocatedBindable; + struct IPSMover; +} + +namespace NLQT { +/** + @class CPSInitialPos + This class helps to copy the position of initial instances in a particle + system. This enable a system to run, and have its parameter modified. + When the user press stop, he will find the system at t = 0, with the new parameters +*/ +class CPSInitialPos +{ +public: + CPSInitialPos() : _PS(NULL) {} + + /// construct this by copying the datas of the system + void copySystemInitialPos(NL3D::CParticleSystem *ps); + + /// reinitialize the system with its initial instances positions + /// Works only once per copySystemInitialPos() call + void restoreSystem(); + + /// send back true when bbox display is enabled + bool isBBoxDisplayEnabled(); + + /// update data when a located in a particle system has been removed + void removeLocated(NL3D::CPSLocated *loc); + + /// update data when a located bindable in a particle system has been removed + void removeLocatedBindable(NL3D::CPSLocatedBindable *lb); + + /// initial position and speed of a located instance in a particle system + struct CInitPSInstanceInfo + { + uint32 Index; + NL3D::CPSLocated *Loc; + NLMISC::CVector Speed; + NLMISC::CVector Pos; + }; + + /// rotation and scale of an element + struct CRotScaleInfo + { + uint32 Index; + NL3D::CPSLocated *Loc; + NL3D::CPSLocatedBindable *LB; + NL3D::IPSMover *Psm; + NLMISC::CMatrix Rot; + NLMISC::CVector Scale; + }; + NL3D::CParticleSystem *getPS() { return _PS; } + const NL3D::CParticleSystem *getPS() const { return _PS; } + bool isStateMemorized() const { return _PS != NULL; } + +private: + typedef std::vector TInitInfoVect; + typedef std::vector TRotScaleInfoVect; + typedef std::vector< std::pair > TInitialLocatedSizeVect; + TInitInfoVect _InitInfoVect; + TRotScaleInfoVect _RotScaleInfoVect; + + /// initial number of instances for each located + TInitialLocatedSizeVect _InitialSizeVect; + NL3D::CParticleSystem *_PS; + + /// reset all initial infos + void reset(); +}; + +} /* namespace NLQT */ + +#endif \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/ps_mover_form.ui b/code/nel/tools/3d/object_viewer_qt/src/ps_mover_form.ui new file mode 100644 index 000000000..ba461eb5c --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/ps_mover_form.ui @@ -0,0 +1,193 @@ + + + CPSMoverPage + + + + 0 + 0 + 332 + 575 + + + + Form + + + + + + Position: + + + + + + + + X: + + + + + + + + 0 + 0 + + + + -9999.000000000000000 + + + 9999.000000000000000 + + + + + + + + + + + Y: + + + + + + + + 0 + 0 + + + + -9999.000000000000000 + + + 9999.000000000000000 + + + + + + + + + + + Z: + + + + + + + + 0 + 0 + + + + -9999.000000000000000 + + + 9999.000000000000000 + + + + + + + + + + + + Sub component: + + + + + + + + + + + + Scale X: + + + + + + + + + + Scale Y: + + + + + + + + + + Scale Z: + + + + + + + + + + Scale: + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 106 + + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CDirectionWidget + QWidget +
direction_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.cpp new file mode 100644 index 000000000..fa717591f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.cpp @@ -0,0 +1,245 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "ps_mover_page.h" + +// Qt includes + +// NeL includes + +// Project includes +#include "modules.h" + +namespace NLQT { + +const float epsilon = 10E-3f; + +CPSMoverPage::CPSMoverPage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.scaleWidget->setRange(0.f, 4.f); + _ui.scaleWidget->setWrapper(&_UniformScaleWrapper); + + _ui.scaleXWidget->setRange(0.f, 4.f); + _ui.scaleXWidget->setWrapper(&_XScaleWrapper); + + _ui.scaleYWidget->setRange(0.f, 4.f); + _ui.scaleYWidget->setWrapper(&_YScaleWrapper); + + _ui.scaleZWidget->setRange(0.f, 4.f); + _ui.scaleZWidget->setWrapper(&_ZScaleWrapper); + + _ui.directionWidget->setWrapper(&_DirectionWrapper); + + connect(_ui.xDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setXPosition(double))); + connect(_ui.yDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setYPosition(double))); + connect(_ui.zDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setZPosition(double))); + + connect(_ui.listWidget, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), + this, SLOT(changeSubComponent())); +} + +CPSMoverPage::~CPSMoverPage() +{ +} + +void CPSMoverPage::setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocated *located, uint32 editedLocatedIndex) +{ + _Node = ownerNode; + _EditedLocated = located; + _EditedLocatedIndex = editedLocatedIndex; + + updatePosition(); + + _ui.listWidget->clear(); + hideWrappersWidget(); + + uint numBound = _EditedLocated->getNbBoundObjects(); + + uint nbCandidates = 0; + + for (uint k = 0; k < numBound; ++k) + { + if (dynamic_cast(_EditedLocated->getBoundObject(k))) + { + CLocatedBindableItem *item = new CLocatedBindableItem(QString(_EditedLocated->getBoundObject(k)->getName().c_str()), + _ui.listWidget); + item->setUserData(_EditedLocated->getBoundObject(k)); + ++nbCandidates; + } + } + if (nbCandidates > 0) + _ui.listWidget->setCurrentRow(0); +} + +void CPSMoverPage::updatePosition(void) +{ + const NLMISC::CVector &pos = _EditedLocated->getPos()[_EditedLocatedIndex]; + + _ui.xDoubleSpinBox->setValue(pos.x); + _ui.yDoubleSpinBox->setValue(pos.y); + _ui.zDoubleSpinBox->setValue(pos.z); + +} + +void CPSMoverPage::hideWrappersWidget() +{ + _ui.scaleLabel->hide(); + _ui.scaleXLabel->hide(); + _ui.scaleYLabel->hide(); + _ui.scaleZLabel->hide(); + _ui.scaleWidget->hide(); + _ui.scaleXWidget->hide(); + _ui.scaleYWidget->hide(); + _ui.scaleZWidget->hide(); + _ui.directionWidget->hide(); +} + +void CPSMoverPage::updateListener(void) +{ +/* if(_ParticleDlg->MainFrame->isMoveElement()) + { + const NLMISC::CVector &pos = _EditedLocated->getPos()[_EditedLocatedIndex]; + NLMISC::CMatrix m; + m = _MouseListener->getModelMatrix(); + m.setPos(pos); + _MouseListener->setModelMatrix(m); + _Node->setModified(true); + }*/ +} + +void CPSMoverPage::setXPosition(double value) +{ + NLMISC::CVector &pos = _EditedLocated->getPos()[_EditedLocatedIndex]; + if (fabs(pos.x - _ui.xDoubleSpinBox->value()) > epsilon) + { + pos.x = value; + updateListener(); + _Node->setModified(true); + } +} + +void CPSMoverPage::setYPosition(double value) +{ + NLMISC::CVector &pos = _EditedLocated->getPos()[_EditedLocatedIndex]; + if (fabs(pos.y - _ui.yDoubleSpinBox->value()) > epsilon) + { + pos.y = value; + updateListener(); + _Node->setModified(true); + } +} + +void CPSMoverPage::setZPosition(double value) +{ + NLMISC::CVector &pos = _EditedLocated->getPos()[_EditedLocatedIndex]; + if (fabs(pos.z - _ui.zDoubleSpinBox->value()) > epsilon) + { + pos.z = value; + updateListener(); + _Node->setModified(true); + } +} + +void CPSMoverPage::changeSubComponent() +{ + hideWrappersWidget(); + NL3D::IPSMover *m = getMoverInterface(); + if (!m) return; + + _Node->getPSPointer()->setCurrentEditedElement(NULL); + _Node->getPSPointer()->setCurrentEditedElement(_EditedLocated, _EditedLocatedIndex, getLocatedBindable()); + + + if (m->supportUniformScaling() && ! m->supportNonUniformScaling() ) + { + _UniformScaleWrapper.OwnerNode = _Node; + _UniformScaleWrapper.M = m; + _UniformScaleWrapper.Index = _EditedLocatedIndex; + + _ui.scaleWidget->updateUi(); + _ui.scaleLabel->show(); + _ui.scaleWidget->show(); + } + else if (m->supportNonUniformScaling()) + { + // dialog for edition of x scale + _XScaleWrapper.OwnerNode = _Node; + _XScaleWrapper.M = m; + _XScaleWrapper.Index = _EditedLocatedIndex; + + _ui.scaleXWidget->updateUi(); + _ui.scaleXLabel->show(); + _ui.scaleXWidget->show(); + + // dialog for edition of y scale + _YScaleWrapper.OwnerNode = _Node; + _YScaleWrapper.M = m; + _YScaleWrapper.Index = _EditedLocatedIndex; + + _ui.scaleYWidget->updateUi(); + _ui.scaleYLabel->show(); + _ui.scaleYWidget->show(); + + // dialog for edition of x scale + _ZScaleWrapper.OwnerNode = _Node; + _ZScaleWrapper.M = m; + _ZScaleWrapper.Index = _EditedLocatedIndex; + + _ui.scaleZWidget->updateUi(); + _ui.scaleZLabel->show(); + _ui.scaleZWidget->show(); + } + + + if (m->onlyStoreNormal()) + { + _DirectionWrapper.OwnerNode = _Node; + _DirectionWrapper.M = m; + _DirectionWrapper.Index = _EditedLocatedIndex; + + _ui.directionWidget->updateUi(); + _ui.directionWidget->show(); + } +} + +NL3D::IPSMover *CPSMoverPage::getMoverInterface(void) +{ + nlassert(_EditedLocated); + sint currIndex = _ui.listWidget->currentRow(); + if (currIndex == -1) return NULL; + + CLocatedBindableItem *item = dynamic_cast(_ui.listWidget->currentItem()); + return dynamic_cast(item->getUserData()); +} + +NL3D::CPSLocatedBindable *CPSMoverPage::getLocatedBindable(void) +{ + nlassert(_EditedLocated); + sint currIndex = _ui.listWidget->currentRow(); + if (currIndex == -1) return NULL; + + CLocatedBindableItem *item = dynamic_cast(_ui.listWidget->currentItem()); + return item->getUserData(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.h b/code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.h new file mode 100644 index 000000000..f2403ca7f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/ps_mover_page.h @@ -0,0 +1,174 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef PS_MOVER_PAGE_H +#define PS_MOVER_PAGE_H + + +#include +#include "ui_ps_mover_form.h" + +// STL includes + +// NeL includes +#include +#include +#include +#include + +// Project includes +#include "ps_wrapper.h" + +namespace NLQT { + +/** +@class CLocatedBindableItem +@brief Contain pointer to CPSLocatedBindable. +*/ +class CLocatedBindableItem: public QListWidgetItem +{ +public: + CLocatedBindableItem ( const QString & text, QListWidget * parent = 0, int type = UserType ): + QListWidgetItem(text, parent, type), _lb(NULL) {} + + void setUserData(NL3D::CPSLocatedBindable *loc) { _lb = loc;} + NL3D::CPSLocatedBindable *getUserData() const { return _lb;} + +private: + + NL3D::CPSLocatedBindable *_lb; +}; /* class CTargetItem */ + +/** +@class CPSMoverPage +@brief Page for QStackWidget, to edit instance in a particle system. +*/ +class CPSMoverPage: public QWidget +{ + Q_OBJECT + +public: + CPSMoverPage(QWidget *parent = 0); + ~CPSMoverPage(); + + /// Set the instance to edit. + void setEditedItem(CWorkspaceNode *ownerNode, NL3D::CPSLocated *located, uint32 editedLocatedIndex); + + /// position has to be updated (for mouse edition) + void updatePosition(void) ; + + /// get the current moving interface, or NULL, if the selected object has no IPSMover interface + NL3D::IPSMover *getMoverInterface(void) ; + + /// get the located being edited + NL3D::CPSLocated *getLocated(void) { return _EditedLocated ; } + const NL3D::CPSLocated *getLocated(void) const { return _EditedLocated ; } + + /// get the index of the current edited item + uint32 getLocatedIndex(void) const { return _EditedLocatedIndex ; } + + /// ghet the current located bindable being edited, or null + NL3D::CPSLocatedBindable *getLocatedBindable(void) ; + +private Q_SLOTS: + void setXPosition(double value); + void setYPosition(double value); + void setZPosition(double value); + void changeSubComponent(); + +private: + + /// wrappers to scale objects + struct CUniformScaleWrapper : public IPSWrapperFloat + { + uint32 Index ; + NL3D::IPSMover *M ; + float get(void) const { return M->getScale(Index).x ; } + void set(const float &v) { M->setScale(Index, v) ; } + } _UniformScaleWrapper ; + + /// wrapper to scale the X coordinate + struct CXScaleWrapper : public IPSWrapperFloat + { + uint32 Index ; + NL3D::IPSMover *M ; + float get(void) const { return M->getScale(Index).x ; } + void set(const float &s) + { + NLMISC::CVector v = M->getScale(Index) ; + M->setScale(Index, NLMISC::CVector(s, v.y, v.z)) ; + } + } _XScaleWrapper ; + + /// wrapper to scale the Y coordinate + struct CYScaleWrapper : public IPSWrapperFloat + { + uint32 Index ; + NL3D::IPSMover *M ; + float get(void) const { return M->getScale(Index).y ; } + void set(const float &s) + { + NLMISC::CVector v = M->getScale(Index) ; + M->setScale(Index, NLMISC::CVector(v.x, s, v.z) ) ; + } + } _YScaleWrapper ; + + /// wrapper to scale the Z coordinate + struct CZScaleWrapper : public IPSWrapperFloat + { + uint32 Index ; + NL3D::IPSMover *M ; + float get(void) const { return M->getScale(Index).z ; } + void set(const float &s) + { + NLMISC::CVector v = M->getScale(Index) ; + M->setScale(Index, NLMISC::CVector(v.x, v.y, s) ) ; + } + } _ZScaleWrapper ; + + /// wrapper for direction + struct CDirectionWrapper : public IPSWrapper + { + uint32 Index ; + NL3D::IPSMover *M ; + NLMISC::CVector get(void) const { return M->getNormal(Index) ; } + void set(const NLMISC::CVector &v) { M->setNormal(Index, v) ; } + + + } _DirectionWrapper ; + + void hideWrappersWidget(); + + /// update the mouse listener position when the user entered a value with the keyboard + void updateListener(void) ; + + CWorkspaceNode *_Node; + + NL3D::CPSLocated *_EditedLocated ; + + uint32 _EditedLocatedIndex ; + + Ui::CPSMoverPage _ui; + +}; /* class CPSMoverPage */ + +} /* namespace NLQT */ + + +#endif // PS_MOVER_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/ps_wrapper.h b/code/nel/tools/3d/object_viewer_qt/src/ps_wrapper.h new file mode 100644 index 000000000..70c1a5aa4 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/ps_wrapper.h @@ -0,0 +1,118 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + + +#ifndef _PS_WRAPPER_H +#define _PS_WRAPPER_H + + + + +#include "nel/misc/rgba.h" +#include "nel/misc/vector.h" +// +#include "nel/3d/ps_attrib_maker.h" +#include "nel/3d/texture.h" +// +#include "particle_node.h" + +namespace NLQT { + +/// Wrapper to read/write a value of type T +template class IPSWrapper +{ +public: + NLQT::CWorkspaceNode *OwnerNode; // Owner node of the property. When the property is modified, then the node will be marked as 'modified' +public: + IPSWrapper() : OwnerNode(NULL) + { + } + // for derivers : get a value + virtual T get(void) const = 0; + void setAndUpdateModifiedFlag(const T &value) + { + if (OwnerNode) + { + OwnerNode->setModified(true); + } + set(value); + } +protected: + // for derivers : set a value + virtual void set(const T &) = 0; +}; + + +/// Wrapper to read/write a scheme of type T +template class IPSSchemeWrapper +{ +public: + NLQT::CWorkspaceNode *OwnerNode; // Owner node of the property. When the property is modified, then the node will be marked as 'modified' +public: + IPSSchemeWrapper() : OwnerNode(NULL) {} + typedef NL3D::CPSAttribMaker scheme_type; + virtual scheme_type *getScheme(void) const = 0; + void setSchemeAndUpdateModifiedFlag(scheme_type *s) + { + if (OwnerNode) + { + OwnerNode->setModified(true); + } + setScheme(s); + } +protected: + virtual void setScheme(scheme_type *s) = 0; +}; + + + +/// RGBA wrapper +typedef IPSWrapper IPSWrapperRGBA; +typedef IPSSchemeWrapper IPSSchemeWrapperRGBA; + +/// float wrapper +typedef IPSWrapper IPSWrapperFloat; +typedef IPSSchemeWrapper IPSSchemeWrapperFloat; + +/// uint wrapper +typedef IPSWrapper IPSWrapperUInt; +typedef IPSSchemeWrapper IPSSchemeWrapperUInt; + +/// sint wrapper +typedef IPSWrapper IPSWrapperInt; + +/// texture wrapper +class IPSWrapperTexture +{ +public: + NLQT::CWorkspaceNode *OwnerNode; +public: + // ctor + IPSWrapperTexture() : OwnerNode(NULL) {} + virtual NL3D::ITexture *get(void) = 0; + virtual void setAndUpdateModifiedFlag(NL3D::ITexture *tex) + { + if (OwnerNode) OwnerNode->setModified(true); + set(tex); + } +protected: + virtual void set(NL3D::ITexture *) = 0; +}; + +} /* namespace NLQT */ + +#endif \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/settings_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/settings_dialog.cpp new file mode 100644 index 000000000..a0df72c2c --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/settings_dialog.cpp @@ -0,0 +1,373 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "settings_dialog.h" + +// Qt includes +#include +#include +#include + +// NeL includes +#include +#include + +// Project includes +#include "modules.h" + +using namespace NLMISC; + +namespace NLQT { + +CSettingsDialog::CSettingsDialog(QWidget *parent) + : QDialog(parent) +{ + ui.setupUi(this); + + loadGraphicsSettings(); + loadSoundSettings(); + loadPathsSettings(); + loadVegetableSettings(); + + connect(ui.enableBloomCheckBox, SIGNAL(toggled(bool)), this, SLOT(setEnableBloom(bool))); + connect(ui.squareBloomCheckBox, SIGNAL(toggled(bool)), this, SLOT(setEnableSquareBloon(bool))); + connect(ui.bloomDensityHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setDensityBloom(int))); + + connect(ui.addToolButton, SIGNAL(clicked()), this, SLOT(addPath())); + connect(ui.removeToolButton, SIGNAL(clicked()), this, SLOT(removePath())); + connect(ui.upToolButton, SIGNAL(clicked()), this, SLOT(upPath())); + connect(ui.downToolButton, SIGNAL(clicked()), this, SLOT(downPath())); + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(applyPressed())); + + connect(ui.tileBankToolButton, SIGNAL(clicked()), this, SLOT(setTileBank())); + connect(ui.tileFarBankToolButton, SIGNAL(clicked()), this, SLOT(setTileFarBank())); + connect(ui.vegetTexToolButton, SIGNAL(clicked()), this, SLOT(setTextureVegetable())); + connect(ui.addZoneToolButton, SIGNAL(clicked()), this, SLOT(addZone())); + connect(ui.removeZoneToolButton, SIGNAL(clicked()), this, SLOT(removeZone())); + +#ifdef NL_OS_UNIX + ui.driverGraphComboBox->setEnabled(false); +#endif + +} + +CSettingsDialog::~CSettingsDialog() +{ + Modules::config().dropCallback("GraphicsDrivers"); + Modules::config().dropCallback("SoundDrivers"); + Modules::config().dropCallback("SearchPaths"); +} + +void CSettingsDialog::addPath() +{ + QListWidgetItem *newItem = new QListWidgetItem; + QString newPath = QFileDialog::getExistingDirectory(this); + if (!(newPath == "")) + { + newItem->setText(newPath); + newItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); + ui.pathsListWidget->addItem(newItem); + } +} + +void CSettingsDialog::removePath() +{ + QListWidgetItem *removeItem = ui.pathsListWidget->takeItem(ui.pathsListWidget->currentRow()); + if (!removeItem) delete removeItem; +} + +void CSettingsDialog::upPath() +{ + sint currentRow = ui.pathsListWidget->currentRow(); + if (!(currentRow == 0)) + { + QListWidgetItem *item = ui.pathsListWidget->takeItem(currentRow); + ui.pathsListWidget->insertItem(--currentRow, item); + ui.pathsListWidget->setCurrentRow(currentRow); + } +} + +void CSettingsDialog::downPath() +{ + sint currentRow = ui.pathsListWidget->currentRow(); + if (!(currentRow == ui.pathsListWidget->count()-1)) + { + QListWidgetItem *item = ui.pathsListWidget->takeItem(currentRow); + ui.pathsListWidget->insertItem(++currentRow, item); + ui.pathsListWidget->setCurrentRow(currentRow); + } +} + +void CSettingsDialog::applyPressed() +{ + + // settings take after restart the program + QMessageBox::warning(this, tr("Settings"), + tr("Graphics and sound settings " + "take after restart the program"), + QMessageBox::Ok); + + saveGraphicsSettings(); + saveSoundSettings(); + savePathsSettings(); + saveVegetableSettings(); + + // save config file + Modules::config().getConfigFile().save(); + + // reload search paths + Modules::config().configSearchPaths(); +} + +void CSettingsDialog::setTileBank() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Set new tile bank"), + ui.tileBankLineEdit->text(), + tr("Tile Bank file (*.smallbank *.bank);;")); + if (!fileName.isEmpty()) + { + ui.tileBankLineEdit->setText(fileName); + } +} + +void CSettingsDialog::setTileFarBank() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Set new tile far bank"), + ui.tileFarBankLineEdit->text(), + tr("Tile Far Bank file (*.farbank);;")); + if (!fileName.isEmpty()) + { + ui.tileFarBankLineEdit->setText(fileName); + } +} + +void CSettingsDialog::setTextureVegetable() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Set MicroVegetable texture"), + ui.vegetTextureLineEdit->text(), + tr("Texture file (*.tga *.png *.jpg *.dds);;")); + if (!fileName.isEmpty()) + { + ui.vegetTextureLineEdit->setText(fileName); + } +} + +void CSettingsDialog::addZone() +{ + QStringList fileNames = QFileDialog::getOpenFileNames(this, + tr("Add zone files"), ".", + tr("Zonel files (*.zonel);;")); + + if (!fileNames.isEmpty()) + { + QStringList list = fileNames; + QStringList::Iterator it = list.begin(); + while(it != list.end()) + { + QListWidgetItem *newItem = new QListWidgetItem; + newItem->setText(*it); + newItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); + ui.zonesListWidget->addItem(newItem); + ++it; + } + } +} + +void CSettingsDialog::removeZone() +{ + QListWidgetItem *removeItem = ui.zonesListWidget->takeItem(ui.zonesListWidget->currentRow()); + if (!removeItem) delete removeItem; +} + +void CSettingsDialog::setEnableBloom(bool state) +{ + Modules::objView().setBloomEffect(state); +} + +void CSettingsDialog::setEnableSquareBloon(bool state) +{ + NL3D::CBloomEffect::instance().setSquareBloom(state); +} + +void CSettingsDialog::setDensityBloom(int density) +{ + NL3D::CBloomEffect::instance().setDensityBloom(density); +} + +void CSettingsDialog::cfcbGraphicsDrivers(NLMISC::CConfigFile::CVar &var) +{ + while (ui.driverGraphComboBox->count()) + ui.driverGraphComboBox->removeItem(0); + + // load types graphics driver from the config file + for (uint i = 0; i < var.size(); ++i) + ui.driverGraphComboBox->addItem(var.asString(i).c_str()); + + // set graphics driver from the config file + QString value = Modules::config().getValue("GraphicsDriver",std::string("OpenGL")).c_str(); + QString dn = value.toLower(); + for (sint i = 0; i < ui.driverGraphComboBox->count(); ++i) + { + if (dn == ui.driverGraphComboBox->itemText(i).toLower()) + { + ui.driverGraphComboBox->setCurrentIndex(i); + return; + } + } +} + +void CSettingsDialog::cfcbSoundDrivers(NLMISC::CConfigFile::CVar& var) +{ + while (ui.driverSndComboBox->count()) + ui.driverSndComboBox->removeItem(0); + + // load types sound driver from the config file + for (uint i = 0; i < var.size(); ++i) + ui.driverSndComboBox->addItem(var.asString(i).c_str()); + + // set sound driver from the config file + QString value = Modules::config().getValue("SoundDriver",std::string("Auto")).c_str(); + QString dn = value.toLower(); + for (sint i = 0; i < ui.driverSndComboBox->count(); ++i) + { + if (dn == ui.driverSndComboBox->itemText(i).toLower()) + { + ui.driverSndComboBox->setCurrentIndex(i); + return; + } + } +} + +void CSettingsDialog::cfcbSearchPaths(NLMISC::CConfigFile::CVar &var) +{ + /// TODO: create custom widget add/insert/del/up/down paths (also this is use landscape zones) + + ui.pathsListWidget->clear(); + + // load search paths from the config file + for (uint i = 0; i < var.size(); ++i) + { + ui.pathsListWidget->addItem(var.asString(i).c_str()); + ui.pathsListWidget->item(i)->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); + } +} + +void CSettingsDialog::loadGraphicsSettings() +{ + // setup config file callbacks and initialize values + Modules::config().setAndCallback("GraphicsDrivers", CConfigCallback(this, &CSettingsDialog::cfcbGraphicsDrivers)); + + ui.enableBloomCheckBox->setChecked(Modules::objView().getBloomEffect()); + ui.squareBloomCheckBox->setChecked(NL3D::CBloomEffect::instance().getSquareBloom()); + ui.bloomDensityHorizontalSlider->setValue(NL3D::CBloomEffect::instance().getDensityBloom()); +} + +void CSettingsDialog::loadSoundSettings() +{ + // setup config file callbacks and initialize values + Modules::config().setAndCallback("SoundDrivers", CConfigCallback(this, &CSettingsDialog::cfcbSoundDrivers)); + + // load settings from the config file + ui.autoLoadSampleCheckBox->setChecked(Modules::config().getValue("SoundAutoLoadSample", true)); + ui.enableOccludeObstructCheckBox->setChecked(Modules::config().getValue("SoundEnableOccludeObstruct", true)); + ui.enableReverbCheckBox->setChecked(Modules::config().getValue("SoundEnableReverb", true)); + ui.manualRolloffCheckBox->setChecked(Modules::config().getValue("SoundManualRolloff", true)); + ui.forceSoftwareCheckBox->setChecked(Modules::config().getValue("SoundForceSoftware", false)); + ui.useADPCMCheckBox->setChecked(Modules::config().getValue("SoundUseADPCM", false)); + ui.maxTrackSpinBox->setValue(Modules::config().getValue("SoundMaxTrack", 48)); +} + +void CSettingsDialog::loadPathsSettings() +{ + // setup config file callbacks and initialize values + Modules::config().setAndCallback("SearchPaths", CConfigCallback(this, &CSettingsDialog::cfcbSearchPaths)); +} + +void CSettingsDialog::loadVegetableSettings() +{ + ui.tileBankLineEdit->setText(Modules::config().getConfigFile().getVar("VegetTileBank").asString().c_str()); + ui.tileFarBankLineEdit->setText(Modules::config().getConfigFile().getVar("VegetTileFarBank").asString().c_str()); + ui.vegetTextureLineEdit->setText(Modules::config().getConfigFile().getVar("VegetTexture").asString().c_str()); + + ui.zonesListWidget->clear(); + + // load vegetable landscape zone paths from config file + NLMISC::CConfigFile::CVar &var = Modules::config().getConfigFile().getVar("VegetLandscapeZones"); + for (uint i = 0; i < var.size(); ++i) + { + ui.zonesListWidget->addItem(var.asString(i).c_str()); + ui.zonesListWidget->item(i)->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); + } +} + +void CSettingsDialog::saveGraphicsSettings() +{ + // save graphics settings to config file + Modules::config().getConfigFile().getVar("GraphicsDriver").setAsString(ui.driverGraphComboBox->currentText().toStdString()); + + Modules::config().getConfigFile().getVar("BloomEffect").setAsInt(ui.enableBloomCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("BloomSquare").setAsInt(ui.squareBloomCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("BloomDensity").setAsInt(ui.bloomDensityHorizontalSlider->value()); +} + +void CSettingsDialog::saveSoundSettings() +{ + // save sound settings to config file + Modules::config().getConfigFile().getVar("SoundDriver").setAsString(ui.driverSndComboBox->currentText().toStdString()); + Modules::config().getConfigFile().getVar("SoundAutoLoadSample").setAsInt(ui.autoLoadSampleCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("SoundEnableOccludeObstruct").setAsInt(ui.enableOccludeObstructCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("SoundEnableReverb").setAsInt(ui.enableReverbCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("SoundManualRolloff").setAsInt(ui.manualRolloffCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("SoundForceSoftware").setAsInt(ui.forceSoftwareCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("SoundUseADPCM").setAsInt(ui.useADPCMCheckBox->isChecked()); + Modules::config().getConfigFile().getVar("SoundMaxTrack").setAsInt(ui.maxTrackSpinBox->value()); +} + +void CSettingsDialog::savePathsSettings() +{ + std::vector list; + for (sint i = 0; i < ui.pathsListWidget->count(); ++i) + { + std::string str = ui.pathsListWidget->item(i)->text().toStdString(); + list.push_back(str); + } + if (ui.pathsListWidget->count() == 0) + list.push_back("WARNING: Delete this string"); + Modules::config().getConfigFile().getVar("SearchPaths").setAsString(list); +} + +void CSettingsDialog::saveVegetableSettings() +{ + Modules::config().getConfigFile().getVar("VegetTileBank").setAsString(ui.tileBankLineEdit->text().toStdString()); + Modules::config().getConfigFile().getVar("VegetTileFarBank").setAsString(ui.tileFarBankLineEdit->text().toStdString()); + Modules::config().getConfigFile().getVar("VegetTexture").setAsString(ui.vegetTextureLineEdit->text().toStdString()); + + std::vector list; + for (sint i = 0; i < ui.zonesListWidget->count(); ++i) + { + std::string str = ui.zonesListWidget->item(i)->text().toStdString(); + list.push_back(str); + } + if (ui.zonesListWidget->count() == 0) + list.push_back("WARNING: Delete this string"); + Modules::config().getConfigFile().getVar("VegetLandscapeZones").setAsString(list); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/settings_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/settings_dialog.h new file mode 100644 index 000000000..258ebb96f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/settings_dialog.h @@ -0,0 +1,84 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SETTINGS_DIALOG_H +#define SETTINGS_DIALOG_H + +#include +#include "ui_settings_form.h" + +// STL includes + +// NeL includes +#include + +// Project includes + +namespace NLQT { + +/** +@class CSettingsDialog +@brief Settings dialog (graphics/sound/search path/vegetable). +*/ +class CSettingsDialog: public QDialog +{ + Q_OBJECT + +public: + CSettingsDialog(QWidget *parent = 0); + ~CSettingsDialog(); + +private Q_SLOTS: + void addPath(); + void removePath(); + void upPath(); + void downPath(); + void applyPressed(); + + void setTileBank(); + void setTileFarBank(); + void setTextureVegetable(); + void addZone(); + void removeZone(); + + void setEnableBloom(bool state); + void setEnableSquareBloon(bool state); + void setDensityBloom(int density); + +private: + void cfcbGraphicsDrivers(NLMISC::CConfigFile::CVar &var); + void cfcbSoundDrivers(NLMISC::CConfigFile::CVar &var); + void cfcbSearchPaths(NLMISC::CConfigFile::CVar &var); + + void loadGraphicsSettings(); + void loadSoundSettings(); + void loadPathsSettings(); + void loadVegetableSettings(); + void saveGraphicsSettings(); + void saveSoundSettings(); + void savePathsSettings(); + void saveVegetableSettings(); + + Ui::CSettingsDialog ui; + +}; /* class CSettingsDialog */ + +} /* namespace NLQT */ + +#endif // SETTINGS_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/settings_form.ui b/code/nel/tools/3d/object_viewer_qt/src/settings_form.ui new file mode 100644 index 000000000..b836a7301 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/settings_form.ui @@ -0,0 +1,657 @@ + + + CSettingsDialog + + + Qt::NonModal + + + + 0 + 0 + 684 + 557 + + + + Settings + + + + :/images/preferences.png:/images/preferences.png + + + true + + + + + + + 0 + 0 + + + + + 120 + 16777215 + + + + + Graphics + + + + + Sound + + + + + Paths + + + + + Vegetable + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + + + + Graphics settings + + + + + + + + Driver + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + Bloom effect + + + + + + + + Bloom density + + + + + + + 255 + + + Qt::Horizontal + + + + + + + + + Square bloom + + + + + + + Enable bloom effect + + + + + + + + + + Qt::Vertical + + + + 20 + 283 + + + + + + + + + + + + Sound settings + + + + + + + + + + Driver + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + SoundAutoLoadSample + + + + + + + SoundEnableOccludeObstruct + + + + + + + SoundEnableReverb + + + + + + + SoundManualRolloff + + + + + + + SoundForceSoftware + + + + + + + SoundUseADPCM + + + + + + + + + SoundMaxTrack + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 89 + + + + + + + + + + + + Search paths + + + + + + + + + + + + 0 + 0 + + + + ... + + + + :/images/list-add.png:/images/list-add.png + + + + 16 + 16 + + + + + + + + ... + + + + :/images/list-remove.png:/images/list-remove.png + + + + + + + ... + + + + :/images/go-up.png:/images/go-up.png + + + + + + + ... + + + + :/images/go-down.png:/images/go-down.png + + + + + + + + + Qt::Vertical + + + + 20 + 195 + + + + + + + + + + + + + + + Setup Landscape + + + + + + Tile bank: + + + + + + + + 0 + 0 + + + + + + + + ... + + + + + + + Tile far bank: + + + + + + + + 0 + 0 + + + + + + + + ... + + + + + + + Vegetable texture: + + + + + + + + 0 + 0 + + + + + + + + ... + + + + + + + Landscape zones: + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 118 + 20 + + + + + + + + QLayout::SetMinimumSize + + + + + + 0 + 0 + + + + ... + + + + :/images/list-add.png:/images/list-add.png + + + + 16 + 16 + + + + + + + + ... + + + + :/images/list-remove.png:/images/list-remove.png + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + listWidget + driverGraphComboBox + driverSndComboBox + autoLoadSampleCheckBox + enableOccludeObstructCheckBox + enableReverbCheckBox + manualRolloffCheckBox + forceSoftwareCheckBox + useADPCMCheckBox + maxTrackSpinBox + pathsListWidget + addToolButton + removeToolButton + upToolButton + downToolButton + buttonBox + + + + + + + buttonBox + accepted() + CSettingsDialog + accept() + + + 222 + 385 + + + 157 + 274 + + + + + buttonBox + rejected() + CSettingsDialog + reject() + + + 290 + 391 + + + 286 + 274 + + + + + listWidget + currentRowChanged(int) + stackedWidget + setCurrentIndex(int) + + + 69 + 96 + + + 226 + 360 + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.cpp new file mode 100644 index 000000000..8da2d799d --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.cpp @@ -0,0 +1,80 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "setup_fog_dialog.h" + +// Qt includes +#include +#include + +// NeL includes +#include +#include + +// Project includes +#include "modules.h" + +using namespace NL3D; +using namespace NLMISC; + +namespace NLQT { + +CSetupFog::CSetupFog(QWidget *parent) + : QDockWidget(parent) +{ + ui.setupUi(this); + + // load fog value from config file + ui.startDoubleSpinBox->setValue(Modules::config().getValue("FogStart", 0.0)); + ui.endDoubleSpinBox->setValue(Modules::config().getValue("FogEnd", 0.0)); + colorFog = Modules::config().getValue("FogColor",CRGBA(0.0, 0.0, 0.0)); + + connect(ui.applyPushButton, SIGNAL(clicked()), this, SLOT(apply())); + connect(ui.colorPushButton, SIGNAL(clicked()), this, SLOT(setColor())); +} + +CSetupFog::~CSetupFog() +{ + // save fog value from config file + Modules::config().getConfigFile().getVar("FogStart").setAsFloat(ui.startDoubleSpinBox->value()); + Modules::config().getConfigFile().getVar("FogEnd").setAsFloat(ui.endDoubleSpinBox->value()); + Modules::config().getConfigFile().getVar("FogColor").setAsInt(colorFog.R, 0); + Modules::config().getConfigFile().getVar("FogColor").setAsInt(colorFog.G, 1); + Modules::config().getConfigFile().getVar("FogColor").setAsInt(colorFog.B, 2); + +} + +void CSetupFog::apply() +{ + Modules::objView().getDriver()->setupFog(ui.startDoubleSpinBox->value(), + ui.endDoubleSpinBox->value(), + colorFog); + Modules::objView().getDriver()->enableFog(ui.enableFogCheckBox->isChecked()); +} + +void CSetupFog::setColor() +{ + QColor color = QColorDialog::getColor(QColor(colorFog.R, + colorFog.G, + colorFog.B)); + colorFog.set(color.red(), color.green(), color.blue()); +} + +} /* namespace NLQT */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.h new file mode 100644 index 000000000..d14ff8d51 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/setup_fog_dialog.h @@ -0,0 +1,60 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SETUP_FOG_DIALOG_H +#define SETUP_FOG_DIALOG_H + +#include +#include "ui_setup_fog_form.h" + +// STL includes + +// NeL includes +#include +#include + +// Project includes + +namespace NLQT { + +/** +@class CSetupFog +@brief Dialog to edit the fog in a 3D scene. +*/ +class CSetupFog: public QDockWidget +{ + Q_OBJECT + +public: + CSetupFog(QWidget *parent = 0); + ~CSetupFog(); + +private Q_SLOTS: + void apply(); + void setColor(); +private: + NLMISC::CRGBA colorFog; + + Ui::CSetupFog ui; + +}; /* class CSetupFog */ + +} /* namespace NLQT */ + +#endif // SETUP_FOG_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/setup_fog_form.ui b/code/nel/tools/3d/object_viewer_qt/src/setup_fog_form.ui new file mode 100644 index 000000000..463cf114a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/setup_fog_form.ui @@ -0,0 +1,242 @@ + + + CSetupFog + + + + 0 + 0 + 135 + 200 + + + + + 135 + 200 + + + + QDockWidget::AllDockWidgetFeatures + + + Setup fog + + + + + + + true + + + + + 0 + 0 + 115 + 159 + + + + + + + + + Start: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + 10000.000000000000000 + + + + + + + + + + + End: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + 2 + + + 100000.000000000000000 + + + 100.000000000000000 + + + + + + + + + Enable fog + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Color + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Apply + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 17 + 62 + + + + + + + + + + + + + + + enableFogCheckBox + toggled(bool) + applyPushButton + click() + + + 24 + 103 + + + 106 + 164 + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.cpp new file mode 100644 index 000000000..b40a0752f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.cpp @@ -0,0 +1,771 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "skeleton_scale_dialog.h" + +// Qt include +#include +#include + +// NeL includes +#include +#include +#include +#include + +// Project includes +#include "modules.h" +#include "skeleton_tree_model.h" + +namespace NLQT { + +const int ssd_scale_precision = 1000; + +CSkeletonScaleDialog::CSkeletonScaleDialog(CSkeletonTreeModel *model, QWidget *parent) + : QDockWidget(parent), _Skeleton(NULL) +{ + _ui.setupUi(this); + + _SaveDirty= false; + + // avoid realloc + _UndoQueue.resize(MaxUndoRedo); + _RedoQueue.resize(MaxUndoRedo); + _UndoQueue.clear(); + _RedoQueue.clear(); + + _BoneBBoxNeedRecompute= false; + + _ui.treeView->setModel(model); + + connect(model, SIGNAL(modelReset()), this, SLOT(resetSkeleton())); + + connect(_ui.treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(setCurrentBone(QModelIndex))); + connect(_ui.xBoneHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setBoneSliderX(int))); + connect(_ui.yBoneHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setBoneSliderY(int))); + connect(_ui.zBoneHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setBoneSliderZ(int))); + connect(_ui.xSkinHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setSkinSliderX(int))); + connect(_ui.ySkinHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setSkinSliderY(int))); + connect(_ui.zSkinHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setSkinSliderZ(int))); + + connect(_ui.xBoneHorizontalSlider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + connect(_ui.yBoneHorizontalSlider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + connect(_ui.zBoneHorizontalSlider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + connect(_ui.xSkinHorizontalSlider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + connect(_ui.ySkinHorizontalSlider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + connect(_ui.zSkinHorizontalSlider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + + connect(_ui.undoPushButton, SIGNAL(clicked()), this, SLOT(clickUndo())); + connect(_ui.redoPushButton, SIGNAL(clicked()), this, SLOT(clickRedo())); + connect(_ui.mirrorPushButton, SIGNAL(clicked()), this, SLOT(clickMirrorSelected())); + connect(_ui.saveAsSkelPushButton, SIGNAL(clicked()), this, SLOT(clickSaveAsSkel())); + connect(_ui.saveSkelPushButton, SIGNAL(clicked()), this, SLOT(clickSaveSkel())); + connect(_ui.saveScalePushButton, SIGNAL(clicked()), this, SLOT(clickSaveScale())); + connect(_ui.loadScalePushButton, SIGNAL(clicked()), this, SLOT(clickLoadScale())); +} + +CSkeletonScaleDialog::~CSkeletonScaleDialog() +{ +} + +void CSkeletonScaleDialog::setCurrentShape(const QString &name) +{ + _ui.treeView->expandAll(); + + if (name.isEmpty()) + return; + + _Skeleton = Modules::objView().getEntity(name.toStdString()).getSkeleton(); + _SkeletonFileName = Modules::objView().getEntity(name.toStdString()).getFileNameSkeleton(); + + // Setup Bone mirror + _Bones.clear(); + if(!_Skeleton.empty()) + { + _Bones.resize(_Skeleton.getNumBones()); + + // copy from skel to mirror + applySkeletonToMirror(); + + _ui.boneGroupBox->setEnabled(true); + _ui.skinGroupBox->setEnabled(true); + _ui.buttonsGroupBox->setEnabled(true); + } + + // reset bone bbox here + _BoneBBoxes.clear(); + _BoneBBoxes.resize(_Bones.size()); + + // delegate to drawSelection(), cause skins not still bound + _BoneBBoxNeedRecompute= true; + + // clean undo/redo + _UndoQueue.clear(); + _RedoQueue.clear(); + refreshUndoRedoView(); + + // clear save button + _SaveDirty= false; + refreshSaveButton(); + _BkupBones = _Bones; +} + +void CSkeletonScaleDialog::setCurrentBone(const QModelIndex & index) +{ + CSkeletonTreeItem *currentItem = static_cast(index.internalPointer()); + + // bkup for undo + static TBoneMirrorArray precState; + precState = _Bones; + + // TODO: multiple selection + for(size_t i = 0; i < _Bones.size(); i++) + _Bones[i].Selected= false; + + _Bones[currentItem->getId()].Selected = true; + + // undo-redo + // selection change => no need to dirt save + pushUndoState(precState, false); + + // refresh text views + updateBoneValues(); +} + +void CSkeletonScaleDialog::setBoneSliderX(int value) +{ + applyScaleSlider(value, SidBoneX); +} + +void CSkeletonScaleDialog::setBoneSliderY(int value) +{ + applyScaleSlider(value, SidBoneY); +} + +void CSkeletonScaleDialog::setBoneSliderZ(int value) +{ + applyScaleSlider(value, SidBoneZ); +} + +void CSkeletonScaleDialog::setSkinSliderX(int value) +{ + applyScaleSlider(value, SidSkinX); +} + +void CSkeletonScaleDialog::setSkinSliderY(int value) +{ + applyScaleSlider(value, SidSkinY); +} + +void CSkeletonScaleDialog::setSkinSliderZ(int value) +{ + applyScaleSlider(value, SidSkinZ); +} + +void CSkeletonScaleDialog::sliderReleased() +{ + // Bkup all scales (dont bother selected bones or which scale is edited...) + _BkupBones = _Bones; + + // push an undo/redo only at release of slide. push the value of scale before slide + // change => must save + pushUndoState(_BkupBones, true); + _SaveDirty = true; + refreshSaveButton(); + + _ui.xBoneHorizontalSlider->setValue(100); + _ui.yBoneHorizontalSlider->setValue(100); + _ui.zBoneHorizontalSlider->setValue(100); + _ui.xSkinHorizontalSlider->setValue(100); + _ui.ySkinHorizontalSlider->setValue(100); + _ui.zSkinHorizontalSlider->setValue(100); +} + +void CSkeletonScaleDialog::clickMirrorSelected() +{ + // bkup for undo + static TBoneMirrorArray precState; + precState = _Bones; + nlassert(!_Skeleton.empty()); + + // for each bone selected + bool change= false; + for(uint i=0;i<_Bones.size();i++) + { + CBoneMirror &bone= _Bones[i]; + if(bone.Selected) + { + // get the bone side and mirrored name + std::string mirrorName; + sint side = getBoneForMirror(i, mirrorName); + // if not a "centered" bone + if(side!=0) + { + // get the bone with mirrored name + sint mirrorId = _Skeleton.getBoneIdByName(mirrorName); + if(mirrorId<0) + { + nlinfo("MirrorScale: Didn't found %s", mirrorName.c_str()); + } + else + { + // copy setup from the dest bone + nlassert(mirrorId<(sint)_Bones.size()); + _Bones[mirrorId].BoneScale= bone.BoneScale; + _Bones[mirrorId].SkinScale= bone.SkinScale; + } + } + } + } + + // refresh display + applyMirrorToSkeleton(); + updateBoneValues(); + + // if some change, bkup for undo/redo + pushUndoState(precState, true); +} + +void CSkeletonScaleDialog::clickUndo() +{ + undo(); +} + +void CSkeletonScaleDialog::clickRedo() +{ + redo(); +} + +void CSkeletonScaleDialog::clickSaveSkel() +{ + // if no skeleton edited, quit + if(_Skeleton.empty()) + return; + + // save the file + NLMISC::COFile f; + if( f.open(_SkeletonFileName) ) + { + if(saveCurrentInStream(f)) + { + // no more need to save (done) + _SaveDirty= false; + refreshSaveButton(); + } + } + else + { + QMessageBox::critical(this, tr("Skeleton scale editor"), tr("Failed to open file for write!"), QMessageBox::Ok); + } +} + +void CSkeletonScaleDialog::clickSaveAsSkel() +{ + // if no skeleton edited, quit + if(_Skeleton.empty()) + return; + + QString fileName = QFileDialog::getSaveFileName(this, tr("Save As Skeleton File"), + ".", + tr("Skeleton files (*.skel);;")); + if (!fileName.isEmpty()) + { + NLMISC::COFile f; + + if( f.open(fileName.toStdString()) ) + { + if(saveCurrentInStream(f)) + { + // no more need to save (done) + _SaveDirty= false; + refreshSaveButton(); + } + + // bkup the valid fileName (new file edited) + _SkeletonFileName = fileName.toStdString(); + } + else + { + QMessageBox::critical(this, tr("Skeleton scale editor"), tr("Failed to open file for write!"), QMessageBox::Ok); + } + } +} + +void CSkeletonScaleDialog::clickLoadScale() +{ + // if no skeleton edited, quit + if(_Skeleton.empty()) + return; + + // choose the file + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open Skeleton Scale File"), ".", + tr("SkelScale files (*.scale);;")); + + setCursor(Qt::WaitCursor); + if (!fileName.isEmpty()) + { + NLMISC::CIFile f; + if( f.open(fileName.toStdString()) ) + loadSkelScaleFromStream(f); + else + QMessageBox::critical(this, tr("Skeleton scale editor"), tr("Failed to open file for read!"), QMessageBox::Ok); + } + setCursor(Qt::ArrowCursor); +} + +void CSkeletonScaleDialog::clickSaveScale() +{ + // if no skeleton edited, quit + if(_Skeleton.empty()) + return; + + // choose the file + QString fileName = QFileDialog::getSaveFileName(this, tr("Save As Skeleton Scale File"), + ".", + tr("SkelScale files (*.scale);;")); + if (!fileName.isEmpty()) + { + NLMISC::COFile f; + if( f.open(fileName.toStdString()) ) + saveSkelScaleInStream(f); + else + QMessageBox::critical(this, tr("Skeleton scale editor"), tr("Failed to open file for write!"), QMessageBox::Ok); + } +} + +void CSkeletonScaleDialog::resetSkeleton() +{ + _ui.boneGroupBox->setEnabled(false); + _ui.skinGroupBox->setEnabled(false); + _ui.buttonsGroupBox->setEnabled(false); + _Skeleton = NULL; +} + +void CSkeletonScaleDialog::updateBoneValues() +{ + // 1.f for each component if multiple selection is different, else 0.f + NLMISC::CVector boneScaleDiff = NLMISC::CVector::Null; + NLMISC::CVector skinScaleDiff = NLMISC::CVector::Null; + + // valid if scale of each bone component is same + NLMISC::CVector boneScaleAll = NLMISC::CVector::Null; + NLMISC::CVector skinScaleAll = NLMISC::CVector::Null; + bool someSelected = false; + + // For all bones selected + for(uint i = 0; i < _Bones.size(); i++) + { + CBoneMirror &bone= _Bones[i]; + if(bone.Selected) + { + if(!someSelected) + { + someSelected= true; + // just bkup + boneScaleAll= bone.BoneScale; + skinScaleAll= bone.SkinScale; + } + else + { + // compare each component, if different, flag + if(boneScaleAll.x!= bone.BoneScale.x) boneScaleDiff.x= 1.f; + if(boneScaleAll.y!= bone.BoneScale.y) boneScaleDiff.y= 1.f; + if(boneScaleAll.z!= bone.BoneScale.z) boneScaleDiff.z= 1.f; + if(skinScaleAll.x!= bone.SkinScale.x) skinScaleDiff.x= 1.f; + if(skinScaleAll.y!= bone.SkinScale.y) skinScaleDiff.y= 1.f; + if(skinScaleAll.z!= bone.SkinScale.z) skinScaleDiff.z= 1.f; + } + } + } + + // if none selected, force empty text + if(!someSelected) + { + boneScaleDiff.set(1.f,1.f,1.f); + skinScaleDiff.set(1.f,1.f,1.f); + } + + // All component refresh or only one refresh? + refreshTextViewWithScale(_ui.xBoneDoubleSpinBox, boneScaleAll.x, boneScaleDiff.x); + refreshTextViewWithScale(_ui.yBoneDoubleSpinBox, boneScaleAll.y, boneScaleDiff.y); + refreshTextViewWithScale(_ui.zBoneDoubleSpinBox, boneScaleAll.z, boneScaleDiff.z); + refreshTextViewWithScale(_ui.xSkinDoubleSpinBox, skinScaleAll.x, skinScaleDiff.x); + refreshTextViewWithScale(_ui.ySkinDoubleSpinBox, skinScaleAll.y, skinScaleDiff.y); + refreshTextViewWithScale(_ui.zSkinDoubleSpinBox, skinScaleAll.z, skinScaleDiff.z); +} + +void CSkeletonScaleDialog::refreshTextViewWithScale(QDoubleSpinBox *spinBox, float scale, float diff) +{ + // if different values selected, diff + if(diff) + spinBox->setValue(0); + // else display text + else + spinBox->setValue(scale * 100 / ssd_scale_precision); +} + +void CSkeletonScaleDialog::applyScaleSlider(int scale, int idSelect) +{ + float factor = scale / 100.0; + // Apply the noise to each selected bones + for(uint i = 0; i < _Bones.size(); i++) + { + if( _Bones[i].Selected) + { + switch(idSelect) + { + case SidBoneX: _Bones[i].BoneScale.x = _BkupBones[i].BoneScale.x * factor; break; + case SidBoneY: _Bones[i].BoneScale.y = _BkupBones[i].BoneScale.y * factor; break; + case SidBoneZ: _Bones[i].BoneScale.z = _BkupBones[i].BoneScale.z * factor; break; + case SidSkinX: _Bones[i].SkinScale.x = _BkupBones[i].SkinScale.x * factor; break; + case SidSkinY: _Bones[i].SkinScale.y = _BkupBones[i].SkinScale.y * factor; break; + case SidSkinZ: _Bones[i].SkinScale.z = _BkupBones[i].SkinScale.z * factor; break; + }; + roundClampScale(_Bones[i].BoneScale); + roundClampScale(_Bones[i].SkinScale); + } + } + // update the skeleton view + applyMirrorToSkeleton(); + + // refresh text views + updateBoneValues(); +} + +void CSkeletonScaleDialog::applyMirrorToSkeleton() +{ + if(!_Skeleton.empty()) + { + nlassert(_Skeleton.getNumBones() == _Bones.size()); + for(uint i = 0; i < _Bones.size(); i++) + { + // unmul from precision + NLMISC::CVector boneScale = _Bones[i].BoneScale / ssd_scale_precision; + NLMISC::CVector skinScale = _Bones[i].SkinScale / ssd_scale_precision; + _Skeleton.getBone(i).setScale(boneScale); + _Skeleton.getBone(i).setSkinScale(skinScale); + } + } +} + +void CSkeletonScaleDialog::applySkeletonToMirror() +{ + if(!_Skeleton.empty()) + { + nlassert(_Skeleton.getNumBones() == _Bones.size()); + for(uint i = 0; i < _Skeleton.getNumBones(); i++) + { + // mul by precision, and round + _Bones[i].SkinScale = _Skeleton.getBone(i).getSkinScale() * ssd_scale_precision; + _Bones[i].BoneScale = _Skeleton.getBone(i).getScale() * ssd_scale_precision; + roundClampScale(_Bones[i].SkinScale); + roundClampScale(_Bones[i].BoneScale); + } + } +} + +void CSkeletonScaleDialog::roundClampScale(NLMISC::CVector &v) const +{ + v.x+= 0.5f; + v.y+= 0.5f; + v.z+= 0.5f; + v.x= (float)floor(v.x); + v.y= (float)floor(v.y); + v.z= (float)floor(v.z); + // Minimum is 1 (avoid 0 scale) + v.maxof(v, NLMISC::CVector(1.f,1.f,1.f)); +} + +void CSkeletonScaleDialog::refreshUndoRedoView() +{ + _ui.undoPushButton->setEnabled(!_UndoQueue.empty()); + _ui.redoPushButton->setEnabled(!_RedoQueue.empty()); +} + +void CSkeletonScaleDialog::applySelectionToView() +{ + _ui.treeView->blockSignals(true); + CSkeletonTreeModel *model = Modules::mainWin().getSkeletonModel(); + for(uint i = 0; i < _Bones.size(); i++) + { + if (_Bones[i].Selected) + _ui.treeView->setCurrentIndex(model->getIndexFromId(i, model->index(0, 0))); + } + _ui.treeView->blockSignals(false); +} + +sint CSkeletonScaleDialog::getBoneForMirror(uint boneId, std::string &mirrorName) +{ + sint side= 0; + std::string::size_type pos; + nlassert(!_Skeleton.empty() && (boneId < _Skeleton.getNumBones())); + mirrorName= _Skeleton.getBone(boneId).getObjectPtr()->getBoneName(); + + if((pos= mirrorName.find(" R "))!=std::string::npos) + { + side= 1; + mirrorName[pos+1]= 'L'; + } + else if((pos= mirrorName.find(" L "))!=std::string::npos) + { + side= -1; + mirrorName[pos+1]= 'R'; + } + + return side; +} + +void CSkeletonScaleDialog::refreshSaveButton() +{ + // SaveAs is always available + _ui.saveSkelPushButton->setEnabled(_SaveDirty); +} + +bool CSkeletonScaleDialog::saveCurrentInStream(NLMISC::IStream &f) +{ + try + { + nlassert(!_Skeleton.empty()); + nlassert(_Skeleton.getObjectPtr()->Shape); + + // Retrieve boneBase definition from the current skeleton + std::vector boneBases; + (NLMISC::safe_cast((NL3D::IShape*)_Skeleton.getObjectPtr()->Shape))->retrieve(boneBases); + + // Copies bone scales from the model + nlassert(boneBases.size() == _Skeleton.getNumBones()); + for(uint i = 0; i < _Skeleton.getNumBones(); i++) + { + NL3D::UBone bone = _Skeleton.getBone(i); + NL3D::CBoneBase &boneBase = boneBases[i]; + + boneBase.SkinScale = bone.getSkinScale(); + boneBase.DefaultScale = bone.getScale(); + } + + // build a new Skeleton shape + NL3D::CSkeletonShape *skelShape= new NL3D::CSkeletonShape; + skelShape->build(boneBases); + + // save + NL3D::CShapeStream ss; + ss.setShapePointer(skelShape); + ss.serial(f); + delete skelShape; + } + catch(NLMISC::EStream &) + { + QMessageBox::critical(this, tr("Skeleton scale editor"), tr("Failed to save file!"), QMessageBox::Ok); + return false; + } + + return true; +} + +struct CBoneScaleInfo +{ + std::string Name; + NLMISC::CVector Scale; + NLMISC::CVector SkinScale; + + void serial(NLMISC::IStream &f) + { + sint32 ver= f.serialVersion(0); + f.serial(Name, Scale, SkinScale); + } +}; + +bool CSkeletonScaleDialog::saveSkelScaleInStream(NLMISC::IStream &f) +{ + try + { + nlassert(!_Skeleton.empty()); + + // Copies bone scales from the model + std::vector boneScales; + boneScales.resize(_Skeleton.getNumBones()); + for(uint i = 0; i < boneScales.size(); i++) + { + NL3D::CBone *bone= _Skeleton.getBone(i).getObjectPtr(); + CBoneScaleInfo &boneScale= boneScales[i]; + + // get scale info from current edited skeleton + boneScale.Name = bone->getBoneName(); + boneScale.Scale = bone->getScale(); + boneScale.SkinScale = bone->getSkinScale(); + } + + // save the file + sint32 ver= f.serialVersion(0); + f.serialCont(boneScales); + } + catch(NLMISC::EStream &) + { + QMessageBox::critical(this, tr("Skeleton scale editor"), tr("Failed to save file!"), QMessageBox::Ok); + return false; + } + + return true; +} + +bool CSkeletonScaleDialog::loadSkelScaleFromStream(NLMISC::IStream &f) +{ + try + { + nlassert(!_Skeleton.empty()); + + // load the file + sint32 ver= f.serialVersion(0); + std::vector boneScales; + f.serialCont(boneScales); + + // apply to the current skeleton + for(uint i = 0; i < boneScales.size(); i++) + { + sint32 boneId = _Skeleton.getBoneIdByName(boneScales[i].Name); + if(boneId >= 0 && boneId < (sint32)_Skeleton.getNumBones()) + { + CBoneScaleInfo &boneScale= boneScales[i]; + _Skeleton.getBone(boneId).setScale(boneScale.Scale); + _Skeleton.getBone(boneId).setSkinScale(boneScale.SkinScale); + } + } + + // Bkup _Bones, for undo + static TBoneMirrorArray precState; + precState = _Bones; + + // Then reapply to the mirror + applySkeletonToMirror(); + + // change => must save + pushUndoState(precState, true); + + // and update display + updateBoneValues(); + } + catch(NLMISC::EStream &) + { + QMessageBox::critical(this, tr("Skeleton scale editor"), tr("Failed to load file!"), QMessageBox::Ok); + return false; + } + + return true; +} + +void CSkeletonScaleDialog::pushUndoState(const TBoneMirrorArray &precState, bool dirtSave) +{ + // test if real change + nlassert(precState.size() == _Bones.size()); + bool change = false; + for(uint i = 0; i < _Bones.size(); i++) + { + if( _Bones[i].BoneScale!=precState[i].BoneScale || + _Bones[i].SkinScale!=precState[i].SkinScale || + _Bones[i].Selected!=precState[i].Selected) + { + change= true; + break; + } + } + // no change? no op + if(!change) + return; + + // then bkup for undo + // change => the redo list is lost + _RedoQueue.clear(); + + // if not enough space, the last undo is lost + if(_UndoQueue.size() == size_t(MaxUndoRedo)) + _UndoQueue.pop_front(); + + // add the precedent state to the undo queue + _UndoQueue.push_back(precState); + + // refresh buttons + refreshUndoRedoView(); + + // refresh save button + if(dirtSave && !_SaveDirty) + { + _SaveDirty = true; + refreshSaveButton(); + } +} + +void CSkeletonScaleDialog::undo() +{ + nlassert(_UndoQueue.size() + _RedoQueue.size() <= size_t(MaxUndoRedo)); + + // is some undoable + if(_UndoQueue.size()) + { + // current goes into the redo queue + _RedoQueue.push_front(_Bones); + // restore + _Bones= _UndoQueue.back(); + // pop + _UndoQueue.pop_back(); + + // refresh display + applyMirrorToSkeleton(); + updateBoneValues(); + applySelectionToView(); + + // refresh buttons + refreshUndoRedoView(); + + // change => must save + _SaveDirty= true; + refreshSaveButton(); + } +} + +void CSkeletonScaleDialog::redo() +{ + nlassert(_UndoQueue.size() + _RedoQueue.size() <= size_t(MaxUndoRedo)); + + // is some redoable + if(_RedoQueue.size()) + { + // current goes into the undo queue + _UndoQueue.push_back(_Bones); + // restore + _Bones= _RedoQueue.front(); + // pop + _RedoQueue.pop_front(); + + // refresh display + applyMirrorToSkeleton(); + updateBoneValues(); + applySelectionToView(); + + // refresh buttons + refreshUndoRedoView(); + + // change => must save + _SaveDirty= true; + refreshSaveButton(); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.h new file mode 100644 index 000000000..91a9b7c96 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_dialog.h @@ -0,0 +1,159 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SKELETON_SCALE_DIALOG_H +#define SKELETON_SCALE_DIALOG_H + +#include "ui_skeleton_scale_form.h" + +// STL includes + +// NeL includes +#include "nel/misc/smart_ptr.h" +#include "nel/misc/stream.h" +#include "nel/misc/vector.h" +#include "nel/misc/aabbox.h" +#include + +// Project includes +#include "skeleton_tree_model.h" + +namespace NLQT { + +/// A mirror to the list of bone +struct CBoneMirror +{ + CBoneMirror() + { + SkinScale= BoneScale= NLMISC::CVector(1.f,1.f,1.f); + Selected= false; + } + + /// Current Scale * ssd_scale_precision, and rounded + NLMISC::CVector SkinScale; + NLMISC::CVector BoneScale; + + /// If the bone is selected in the multi List + bool Selected; +}; + +typedef std::vector TBoneMirrorArray; + +/** +@class CSkeletonScaleDialog +@brief Dialog to edit the skeleton. +*/ +class CSkeletonScaleDialog: public QDockWidget +{ + Q_OBJECT + +public: + CSkeletonScaleDialog(CSkeletonTreeModel *model, QWidget *parent = 0); + ~CSkeletonScaleDialog(); + + /// call each frame to display scaled bboxes around selected bones + void drawSelection(); + +public Q_SLOTS: + /// needs called when the changes current edited skeleton + void setCurrentShape(const QString &name); + +private Q_SLOTS: + void setCurrentBone(const QModelIndex & index); + void setBoneSliderX(int value); + void setBoneSliderY(int value); + void setBoneSliderZ(int value); + void setSkinSliderX(int value); + void setSkinSliderY(int value); + void setSkinSliderZ(int value); + void sliderReleased(); + void clickMirrorSelected(); + void clickUndo(); + void clickRedo(); + void clickSaveSkel(); + void clickSaveAsSkel(); + void clickLoadScale(); + void clickSaveScale(); + void resetSkeleton(); + +private: + void updateBoneValues(); + void refreshTextViewWithScale(QDoubleSpinBox *spinBox, float scale, float diff); + void applyScaleSlider(int scale, int idSelect); + void applyMirrorToSkeleton(); + void applySkeletonToMirror(); + void roundClampScale(NLMISC::CVector &v) const; + void refreshUndoRedoView(); + void applySelectionToView(); + sint getBoneForMirror(uint boneId, std::string &mirrorName); + bool saveCurrentInStream(NLMISC::IStream &f); + void refreshSaveButton(); + bool saveSkelScaleInStream(NLMISC::IStream &f); + bool loadSkelScaleFromStream(NLMISC::IStream &f); + + /// bkup the current _Bones in the undo queue, and clear the redo. dirtSave indicate the skel need saving + /// NB: compare precState with _Bones. if same, then no-op! + void pushUndoState(const TBoneMirrorArray &precState, bool dirtSave); + + /// undo the last change, and store it in the redo queue + void undo(); + + /// redo the last undoed change, and restore it in the undo queue + void redo(); + + enum TScaleId + { + SidBoneX = 0, + SidBoneY, + SidBoneZ, + SidSkinX, + SidSkinY, + SidSkinZ, + SidCount, + SidNone = SidCount + }; + + NL3D::USkeleton _Skeleton; + + std::string _SkeletonFileName; + + // The current bones + TBoneMirrorArray _Bones; + + // Backup of bones when slider start moving + TBoneMirrorArray _BkupBones; + + bool _SaveDirty; + + // For selection drawing, the local bbox + std::vector _BoneBBoxes; + bool _BoneBBoxNeedRecompute; + + std::deque _UndoQueue; + std::deque _RedoQueue; + + static const int MaxUndoRedo = 100; + + Ui::CSkeletonScaleDialog _ui; + +}; /* class CSkeletonScaleDialog */ + +} /* namespace NLQT */ + +#endif // SKELETON_SCALE_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_form.ui b/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_form.ui new file mode 100644 index 000000000..28fed61de --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/skeleton_scale_form.ui @@ -0,0 +1,633 @@ + + + CSkeletonScaleDialog + + + + 0 + 0 + 306 + 694 + + + + Skeleton scale edition + + + + + + + true + + + true + + + + + + + false + + + Bone scale + + + + + + X: + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 10000.000000000000000 + + + + + + + 50 + + + 200 + + + 100 + + + Qt::Horizontal + + + false + + + + + + + + 16777215 + 16777215 + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 50 + + + 200 + + + 100 + + + + + + + Y: + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 10000.000000000000000 + + + + + + + 50 + + + 200 + + + 100 + + + Qt::Horizontal + + + + + + + + 16777215 + 16777215 + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 50 + + + 200 + + + 100 + + + + + + + Z: + + + + + + + false + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 10000.000000000000000 + + + + + + + 50 + + + 200 + + + 100 + + + Qt::Horizontal + + + + + + + + 16777215 + 16777215 + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 50 + + + 200 + + + 100 + + + + + + + + + + false + + + Skin scale (slower) + + + + + + X: + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 10000.000000000000000 + + + + + + + 50 + + + 200 + + + 100 + + + Qt::Horizontal + + + false + + + + + + + + 16777215 + 16777215 + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 50 + + + 200 + + + 100 + + + + + + + + 16777215 + 16777215 + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 50 + + + 200 + + + 100 + + + + + + + Y: + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 10000.000000000000000 + + + + + + + 50 + + + 200 + + + 100 + + + Qt::Horizontal + + + + + + + Z: + + + + + + + false + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 10000.000000000000000 + + + + + + + 50 + + + 200 + + + 100 + + + Qt::Horizontal + + + + + + + + 16777215 + 16777215 + + + + true + + + QAbstractSpinBox::NoButtons + + + % + + + 50 + + + 200 + + + 100 + + + + + + + + + + false + + + + + + + + + + + Mirror selected + + + + + + + Undo + + + + + + + Redo + + + + + + + Save .skel + + + + + + + Save as + + + + + + + Load .scale + + + + + + + Save .scale + + + + + + + + + + + + + + + xBoneHorizontalSlider + valueChanged(int) + spinBox_4 + setValue(int) + + + 188 + 312 + + + 231 + 320 + + + + + yBoneHorizontalSlider + valueChanged(int) + spinBox_5 + setValue(int) + + + 207 + 337 + + + 240 + 340 + + + + + zBoneHorizontalSlider + valueChanged(int) + spinBox_6 + setValue(int) + + + 207 + 358 + + + 240 + 366 + + + + + xSkinHorizontalSlider + valueChanged(int) + spinBox + setValue(int) + + + 207 + 431 + + + 272 + 438 + + + + + ySkinHorizontalSlider + valueChanged(int) + spinBox_2 + setValue(int) + + + 207 + 462 + + + 267 + 467 + + + + + zSkinHorizontalSlider + valueChanged(int) + spinBox_3 + setValue(int) + + + 207 + 491 + + + 233 + 493 + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.cpp b/code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.cpp new file mode 100644 index 000000000..591ff59c1 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.cpp @@ -0,0 +1,197 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "skeleton_tree_model.h" + +// NeL include +#include +#include + +// Project includes +#include "modules.h" + +namespace NLQT { + +CSkeletonTreeModel::CSkeletonTreeModel(QObject *parent) + : QAbstractItemModel(parent) +{ + QList rootData; + rootData << "Skeleton"; + _rootItem = new CSkeletonTreeItem(rootData, -1); +} + +CSkeletonTreeModel::~CSkeletonTreeModel() +{ + delete _rootItem; +} + +int CSkeletonTreeModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return static_cast(parent.internalPointer())->columnCount(); + else + return _rootItem->columnCount(); +} + +QVariant CSkeletonTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + CSkeletonTreeItem *item = static_cast(index.internalPointer()); + + return item->data(index.column()); +} + +Qt::ItemFlags CSkeletonTreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +QVariant CSkeletonTreeModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return _rootItem->data(section); + + return QVariant(); +} + +QModelIndex CSkeletonTreeModel::index(int row, int column, const QModelIndex &parent) + const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + CSkeletonTreeItem *parentItem; + + if (!parent.isValid()) + parentItem = _rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + CSkeletonTreeItem *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex CSkeletonTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + CSkeletonTreeItem *childItem = static_cast(index.internalPointer()); + CSkeletonTreeItem *parentItem = childItem->parent(); + + if (parentItem == _rootItem) + return QModelIndex(); + + return createIndex(parentItem->row(), 0, parentItem); +} + +int CSkeletonTreeModel::rowCount(const QModelIndex &parent) const +{ + CSkeletonTreeItem *parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = _rootItem; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->childCount(); +} + +void CSkeletonTreeModel::rebuildModel() +{ + std::string curObj = Modules::objView().getCurrentObject(); + + NL3D::USkeleton skel = Modules::objView().getEntity(curObj).getSkeleton(); + + if (skel.empty()) + { + resetTreeModel(); + return; + } + + beginResetModel(); + delete _rootItem; + + QList rootData; + rootData << curObj.c_str(); + + _rootItem = new CSkeletonTreeItem(rootData, -1); + + CSkeletonTreeItem *parentItem = _rootItem; + + for (uint i = 0; i < skel.getNumBones(); i++) + { + NL3D::UBone bone = skel.getBone(i); + sint32 parentId = bone.getObjectPtr()->getFatherId(); + + while(parentId != parentItem->getId()) + parentItem = parentItem->parent(); + + QList boneData; + boneData << bone.getObjectPtr()->getBoneName().c_str(); + CSkeletonTreeItem *item = new CSkeletonTreeItem(boneData, i, parentItem); + parentItem->appendChild(item); + parentItem = item; + } + + endResetModel(); +} + +void CSkeletonTreeModel::resetTreeModel() +{ + beginResetModel(); + delete _rootItem; + QList rootData; + rootData << "Skeleton"; + _rootItem = new CSkeletonTreeItem(rootData, -1); + endResetModel(); +} + +QModelIndex CSkeletonTreeModel::getIndexFromId(sint id, const QModelIndex &parent) +{ + QModelIndex currentIndex = parent; + CSkeletonTreeItem *item = static_cast(parent.internalPointer()); + if (item->getId() != id) + for (int i = 0; i < item->childCount(); i++) + { + currentIndex = getIndexFromId(id, index(i, 0, parent)); + CSkeletonTreeItem *item = static_cast(currentIndex.internalPointer()); + if (item->getId() == id) + return currentIndex; + } + return currentIndex; +} + + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.h b/code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.h new file mode 100644 index 000000000..ad038cef6 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/skeleton_tree_model.h @@ -0,0 +1,109 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SKELETON_TREE_MODEL_H +#define SKELETON_TREE_MODEL_H + + +// Qt includes +#include +#include +#include +#include +#include + +// NeL includes +#include "nel/misc/types_nl.h" + +// Projects includes + +namespace NLQT { + +/** +@class CSkeletonTreeItem +@brief Basic elements tree model skeleton. +*/ +class CSkeletonTreeItem +{ +public: + CSkeletonTreeItem(const QList &data, const sint32 id, CSkeletonTreeItem *parent = 0) + : _itemData(data), _id(id), _parentItem(parent) {} + + ~CSkeletonTreeItem() { qDeleteAll(_childItems); } + + void appendChild(CSkeletonTreeItem *child) { _childItems.append(child); } + + CSkeletonTreeItem *child(int row) { return _childItems.value(row); } + int childCount() const { return _childItems.count(); } + int columnCount() const { return _itemData.count(); } + QVariant data(int column) const { return _itemData.value(column); } + + int row() const { if (_parentItem) + return _parentItem->_childItems.indexOf(const_cast(this)); + return 0; } + + CSkeletonTreeItem *parent() { return _parentItem; } + sint32 getId() { return _id; } + +private: + + QList _childItems; + QList _itemData; + sint32 _id; + CSkeletonTreeItem *_parentItem; +}; + +/** +@class CSkeletonTreeModel +@brief Tree model skeleton. +*/ +class CSkeletonTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + CSkeletonTreeModel(QObject *parent = 0); + ~CSkeletonTreeModel(); + + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QModelIndex getIndexFromId(sint id, const QModelIndex &parent); + + /// Clear tree model skeleton + void resetTreeModel(); + +public Q_SLOTS: + + /// Update tree model skeleton + void rebuildModel(); + +private: + + CSkeletonTreeItem *_rootItem; +}; + +} /* namespace NLQT */ + +#endif // SKELETON_TREE_MODEL_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/slot_form.ui b/code/nel/tools/3d/object_viewer_qt/src/slot_form.ui new file mode 100644 index 000000000..e058d5145 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/slot_form.ui @@ -0,0 +1,249 @@ + + + CSlotGroupBox + + + + 0 + 0 + 265 + 358 + + + + + 99999 + 9999 + + + + GroupBox + + + Slot _:empty + + + false + + + + + + + + QFrame::NoFrame + + + Start blend + + + + + + + 1 + + + + + + + End blend + + + + + + + 1 + + + + + + + Offset frame + + + + + + + 99999 + + + + + + + Speed x + + + + + + + 0.010000000000000 + + + 100.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Start frame + + + + + + + 999999 + + + + + + + End frame + + + + + + + 999999 + + + + + + + Smooth + + + + + + + 1 + + + + + + + + + + + Wrap mode: + + + + + + + Clamp + + + true + + + + + + + Repeat + + + + + + + Disable + + + + + + + + + + + + 0 + 0 + + + + Set anim + + + + + + + + 0 + 0 + + + + Set skel + + + + + + + true + + + + 0 + 0 + + + + Align blend + + + + + + + + + + + Enable + + + true + + + + + + + Invert skeleton weight + + + + + + + + + + diff --git a/code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.cpp new file mode 100644 index 000000000..9cf6a50ca --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.cpp @@ -0,0 +1,284 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "slot_manager_dialog.h" + +// Qt includes +#include + +// NeL includes + +// Project includes +#include "modules.h" + +namespace NLQT { + +CSlotGroupBox::CSlotGroupBox(QWidget *parent) + : QGroupBox(parent), + _animName("empty"), _skelName("empty"), _numSlot(0) +{ + _ui.setupUi(this); + + connect(_ui.animPushButton, SIGNAL(clicked()), this, SLOT(selectAnim())); + connect(_ui.skelPushButton, SIGNAL(clicked()), this, SLOT(selectSkel())); + connect(_ui.alignBlendPushButton, SIGNAL(clicked()), this, SLOT(alignAblend())); + connect(_ui.clampRadioButton, SIGNAL(clicked()), this, SLOT(saveSlotInfo())); + connect(_ui.repeatRadioButton, SIGNAL(clicked()), this, SLOT(saveSlotInfo())); + connect(_ui.disableRadioButton, SIGNAL(clicked()), this, SLOT(saveSlotInfo())); + connect(_ui.invSkelWeightCheckBox, SIGNAL(clicked()), this, SLOT(saveSlotInfo())); + connect(_ui.endBlendSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveSlotInfo())); + connect(_ui.endFrameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveSlotInfo())); + connect(_ui.offsetSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveSlotInfo())); + connect(_ui.smoothSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveSlotInfo())); + connect(_ui.speedDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(saveSlotInfo())); + connect(_ui.startBlendSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveSlotInfo())); + connect(_ui.startFrameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveSlotInfo())); + connect(_ui.enableCheckBox, SIGNAL(clicked()), this, SLOT(saveSlotInfo())); +} + +CSlotGroupBox::~CSlotGroupBox() +{ +} + +void CSlotGroupBox::updateUi() +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + { + resetUi(); + return; + } + CEntity &entity = Modules::objView().getEntity(curObj); + CSlotInfo slotInfo = entity.getSlotInfo(_numSlot); + + _ui.offsetSpinBox->setValue(slotInfo.Offset * Modules::mainWin().getFrameRate()); + _ui.startBlendSpinBox->setValue(int(slotInfo.StartBlend)); + _ui.endBlendSpinBox->setValue(int(slotInfo.EndBlend)); + _ui.startFrameSpinBox->setValue(int(slotInfo.StartTime * Modules::mainWin().getFrameRate())); + _ui.endFrameSpinBox->setValue(int(slotInfo.EndTime * Modules::mainWin().getFrameRate())); + _ui.speedDoubleSpinBox->setValue(slotInfo.SpeedFactor); + _ui.smoothSpinBox->setValue(int(slotInfo.Smoothness)); + + if (slotInfo.SkeletonInverted) + _ui.invSkelWeightCheckBox->setCheckState(Qt::Checked); + else + _ui.invSkelWeightCheckBox->setCheckState(Qt::Unchecked); + + if (slotInfo.Enable) + _ui.enableCheckBox->setCheckState(Qt::Checked); + else + _ui.enableCheckBox->setCheckState(Qt::Unchecked); + + switch (slotInfo.ClampMode) + { + case 0: + _ui.clampRadioButton->setChecked(true); + break; + case 1: + _ui.repeatRadioButton->setChecked(true); + break; + case 2: + _ui.disableRadioButton->setChecked(true); + break; + } + + _animName = QString(slotInfo.Animation.c_str()); + _skelName = QString(slotInfo.Skeleton.c_str()); + QString title = tr("Slot %1 : ").arg(_numSlot) + _animName + " : " + _skelName; + this->setTitle(title); +} + +void CSlotGroupBox::saveSlotInfo() +{ + CSlotInfo slotInfo; + slotInfo.Animation = _animName.toStdString(); + slotInfo.Skeleton = _skelName.toStdString(); + slotInfo.EndBlend = _ui.endBlendSpinBox->value(); + slotInfo.EndTime = float(_ui.endFrameSpinBox->value()) / Modules::mainWin().getFrameRate(); + slotInfo.Offset = float(_ui.offsetSpinBox->value()) / Modules::mainWin().getFrameRate(); + slotInfo.Smoothness = _ui.smoothSpinBox->value(); + slotInfo.SpeedFactor = _ui.speedDoubleSpinBox->value(); + slotInfo.StartBlend = _ui.startBlendSpinBox->value(); + slotInfo.StartTime = float(_ui.startFrameSpinBox->value()) / Modules::mainWin().getFrameRate(); + + if (_ui.invSkelWeightCheckBox->checkState() == Qt::Checked) + slotInfo.SkeletonInverted = true; + else + slotInfo.SkeletonInverted = false; + + if (_ui.enableCheckBox->checkState() == Qt::Checked) + slotInfo.Enable = true; + else + slotInfo.Enable = false; + + if (_ui.clampRadioButton->isChecked()) + slotInfo.ClampMode = 0; + else if (_ui.repeatRadioButton->isChecked()) + slotInfo.ClampMode = 1; + else + slotInfo.ClampMode = 2; + + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + entity.setSlotInfo(_numSlot, slotInfo); +} + +void CSlotGroupBox::resetUi() +{ + _ui.offsetSpinBox->setValue(0); + _ui.startBlendSpinBox->setValue(0); + _ui.endBlendSpinBox->setValue(0); + _ui.startFrameSpinBox->setValue(0); + _ui.endFrameSpinBox->setValue(0); + _ui.speedDoubleSpinBox->setValue(0); + _ui.smoothSpinBox->setValue(0); + _ui.disableRadioButton->setChecked(true); + _ui.invSkelWeightCheckBox->setCheckState(Qt::Unchecked); + _ui.enableCheckBox->setCheckState(Qt::Unchecked); +} + +void CSlotGroupBox::selectAnim() +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + std::vector& animationList = entity.getAnimationList(); + + if (animationList.empty()) return; + + QStringList items; + items << tr("empty"); + for(size_t i = 0; i < animationList.size(); ++i) + items << QString(animationList[i].c_str()); + + bool ok; + QString item = QInputDialog::getItem(this, tr("Select your anim"), + tr("Animation:"), items, 0, false, &ok); + if (ok) + { + _animName = item; + QString title = tr("Slot %1 : ").arg(_numSlot) + _animName + " : " + _skelName; + this->setTitle(title); + _ui.endFrameSpinBox->setValue(int(entity.getAnimLength(_animName.toStdString()) * Modules::mainWin().getFrameRate())); + saveSlotInfo(); + } +} + +void CSlotGroupBox::selectSkel() +{ + std::string curObj = Modules::objView().getCurrentObject(); + if (curObj.empty()) + return; + CEntity &entity = Modules::objView().getEntity(curObj); + std::vector& swtList = entity.getSWTList(); + + if (swtList.empty()) return; + + QStringList items; + items << tr("empty"); + for(size_t i = 0; i < swtList.size(); ++i) + items << QString(swtList[i].c_str()); + + bool ok; + QString item = QInputDialog::getItem(this, tr("Select your skel"), + tr("Skeleton weight template:"), items, 0, false, &ok); + if (ok) + { + _skelName = item; + QString title = tr("Slot %1 : ").arg(_numSlot) + _animName + " : " + _skelName; + this->setTitle(title); + saveSlotInfo(); + } +} + +void CSlotGroupBox::alignAblend() +{ + float deltaTime = _ui.endFrameSpinBox->value() - _ui.startFrameSpinBox->value(); + _ui.startFrameSpinBox->setValue(_ui.offsetSpinBox->value()); + _ui.endFrameSpinBox->setValue(int(float(_ui.offsetSpinBox->value()) + deltaTime / _ui.speedDoubleSpinBox->value())); +} + +CSlotManagerDialog::CSlotManagerDialog(QWidget *parent) + : QDockWidget(parent) +{ + setObjectName(QString::fromUtf8("CSlotManagerDialog")); + QIcon icon; + icon.addFile(QString::fromUtf8(":/images/mixer.png"), QSize(), QIcon::Normal, QIcon::Off); + setWindowIcon(icon); + setMinimumSize(QSize(310, 100)); + _dockWidgetContents = new QWidget(); + _gridLayout = new QGridLayout(_dockWidgetContents); + _scrollArea = new QScrollArea(_dockWidgetContents); + _scrollArea->setWidgetResizable(true); + _scrollAreaWidgetContents = new QWidget(); + + _slotGridLayout = new QGridLayout(_scrollAreaWidgetContents); + + _tabWidget = new QTabWidget(_scrollAreaWidgetContents); + _tabWidget->setObjectName(QString::fromUtf8("_tabWidget")); + _tabWidget->setTabPosition(QTabWidget::East); + + for (int i = 0; i < NL3D::CChannelMixer::NumAnimationSlot; i++) + { + _tabs[i] = new QWidget(); + _tabs[i]->setObjectName(QString::fromUtf8("_tab%1").arg(i)); + _gridLayouts[i] = new QGridLayout(_tabs[i]); + _gridLayouts[i]->setObjectName(QString::fromUtf8("_gridLayouts%1").arg(i)); + + _slotGroupBoxs[i] = new CSlotGroupBox(_tabs[i]); + _slotGroupBoxs[i]->setTitle(tr("Slot %1 : empty : empty").arg(i)); + _slotGroupBoxs[i]->_numSlot = i; + _gridLayouts[i]->addWidget(_slotGroupBoxs[i], 0, 0, 1, 1); + _verticalSpacers[i] = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + _gridLayouts[i]->addItem(_verticalSpacers[i], 1, 0, 1, 1); + + _tabWidget->addTab(_tabs[i], QString()); + _tabWidget->setTabText(i, tr("Slot %1").arg(i)); + + + } + + _slotGridLayout->addWidget(_tabWidget); + + _scrollArea->setWidget(_scrollAreaWidgetContents); + + _gridLayout->addWidget(_scrollArea, 0, 0, 1, 1); + + setWidget(_dockWidgetContents); + setWindowTitle(tr("Slot manager")); +} + +CSlotManagerDialog::~CSlotManagerDialog() +{ +} + +void CSlotManagerDialog::updateUiSlots() +{ + for (int i = 0; i < NL3D::CChannelMixer::NumAnimationSlot; i++) + { + _slotGroupBoxs[i]->updateUi(); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.h new file mode 100644 index 000000000..adbc4f83e --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/slot_manager_dialog.h @@ -0,0 +1,86 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SLOT_MANAGER_DIALOG_H +#define SLOT_MANAGER_DIALOG_H + +#include +#include "ui_slot_form.h" + +#include +#include + +#include "nel/3d/channel_mixer.h" + +namespace NLQT { + +class CSlotGroupBox: public QGroupBox +{ + Q_OBJECT +public: + CSlotGroupBox(QWidget *parent = 0); + ~CSlotGroupBox(); + void resetUi(); + void updateUi(); + +public Q_SLOTS: + void saveSlotInfo(); + +private Q_SLOTS: + void selectAnim(); + void selectSkel(); + void alignAblend(); + +private: + + QString _animName, _skelName; + int _numSlot; + Ui::CSlotGroupBox _ui; + + friend class CSlotManagerDialog; +}; + +class CSlotManagerDialog: public QDockWidget +{ + Q_OBJECT + +public: + CSlotManagerDialog(QWidget *parent = 0); + ~CSlotManagerDialog(); + +public Q_SLOTS: + void updateUiSlots(); + +private: + QWidget *_dockWidgetContents; + QGridLayout *_gridLayout; + QGridLayout *_slotGridLayout; + QScrollArea *_scrollArea; + QWidget *_scrollAreaWidgetContents; + QTabWidget *_tabWidget; + QSpacerItem *_verticalSpacers[NL3D::CChannelMixer::NumAnimationSlot]; + QGridLayout *_gridLayouts[NL3D::CChannelMixer::NumAnimationSlot]; + QWidget *_tabs[NL3D::CChannelMixer::NumAnimationSlot]; + CSlotGroupBox *_slotGroupBoxs[NL3D::CChannelMixer::NumAnimationSlot]; + +}; /* CSlotManager */ + +} /* namespace NLQT */ + +#endif // SLOT_MANAGER_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/sound_system.cpp b/code/nel/tools/3d/object_viewer_qt/src/sound_system.cpp new file mode 100644 index 000000000..ad907f275 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/sound_system.cpp @@ -0,0 +1,224 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#include "stdpch.h" +#include "sound_system.h" + +// NeL includes +#include +#include +#include +#include +#include +#include + +// Project includes +#include "modules.h" + +namespace NLQT { + +CSoundSystem::CSoundSystem() + :_AudioMixer(NULL), + _AnimManager(NULL), + _Zero(NLMISC::CVector::Null) +{ + +} + +CSoundSystem::~CSoundSystem() +{ +} + +void CSoundSystem::setListenerMatrix(const NLMISC::CMatrix &m) +{ + if (_AudioMixer) + { + if(m.getPos() != oldMatrix.getPos() || m.getJ() != oldMatrix.getJ() || m.getK() != oldMatrix.getK()) + { + NLSOUND::UListener *l = _AudioMixer->getListener(); + l->setPos(m.getPos()); + l->setOrientation(m.getJ(), m.getK()); + oldMatrix = m; + } + } +} + +void CSoundSystem::init () +{ + //H_AUTO2 + nldebug("CSoundSystem::init"); + + // create audiomixer + _AudioMixer = NULL; + _AnimManager = NULL; + NL3D::UParticleSystemSound::setPSSound(NULL); + _AudioMixer = NLSOUND::UAudioMixer::createAudioMixer(); + nlassert(_AudioMixer); + + try + { + // init audiomixer + _PackedSheetPath = Modules::config().getValue("SoundPackedSheetPath", std::string("")); + _SamplePath = Modules::config().getValue("SoundSamplePath", std::string("")); + _AudioMixer->setSamplePath(_SamplePath); + _AudioMixer->setPackedSheetOption(_PackedSheetPath, true); + std::vector devices; + _AudioMixer->initDriver(Modules::config().getValue("SoundDriver", std::string("Auto"))); + _AudioMixer->getDevices(devices); + NLSOUND::UAudioMixer::CInitInfo audioInfo; + audioInfo.AutoLoadSample = Modules::config().getValue("SoundAutoLoadSample", true); + audioInfo.EnableOccludeObstruct = Modules::config().getValue("SoundEnableOccludeObstruct", true); + audioInfo.EnableReverb = Modules::config().getValue("SoundEnableReverb", true); + audioInfo.ManualRolloff = Modules::config().getValue("SoundManualRolloff", true); + audioInfo.ForceSoftware = Modules::config().getValue("SoundForceSoftware", false); + audioInfo.MaxTrack = Modules::config().getValue("SoundMaxTrack", 48); + audioInfo.UseADPCM = Modules::config().getValue("SoundUseADPCM", false); + _AudioMixer->initDevice(Modules::config().getValue("SoundDevice", std::string("")), audioInfo, NULL); + _AudioMixer->setLowWaterMark(1); + } + catch(NLMISC::Exception &e) + { + // in case of exeption during mixer init, the mixer is destroyed ! + std::string mess = std::string("Unable to init sound :") + e.what(); + nlwarning ("Init sound: %s", mess.c_str()); + _AudioMixer = NULL; + return; + } + + // ok for the mixer, now create the sound anim manager + try + { + // TODO : boris : Hum, as far as I know, throwing exeption in constructor is a very BAD idea... + _AnimManager = new NLSOUND::CSoundAnimManager(_AudioMixer); + } + catch (NLMISC::Exception &e) + { + std::string mess = std::string("Unable to init sound :") + e.what(); + nlwarning ("Init sound: %s", mess.c_str()); + delete _AudioMixer; + _AudioMixer = NULL; + + return; + } + NL3D::UParticleSystemSound::setPSSound(_AudioMixer); +} + +void CSoundSystem::release(void) +{ + //H_AUTO2 + nldebug("CSoundSystem::release"); + + NL3D::UParticleSystemSound::setPSSound(NULL); + if (_AnimManager) + { + delete _AnimManager; + _AnimManager = NULL; + } + if (_AudioMixer) + { + delete _AudioMixer; + _AudioMixer = NULL; + } +} + +void CSoundSystem::play(const std::string &soundName) +{ + if (_AudioMixer) + { + NLSOUND::USource *src = _AudioMixer->createSource(NLMISC::CStringMapper::map(soundName), true); + if (src) + { + src->setLooping(false); + const NLMISC::CVector &pos = _AudioMixer->getListener()->getPos(); + src->setPos(pos); + src->play(); + } + else + { + nlwarning("Can't play the sound (perhaps it's contextual sound)"); + } + } +} + +NLSOUND::USource *CSoundSystem::create(const std::string &soundName) +{ + if (_AudioMixer) + { + NLSOUND::USource *src = _AudioMixer->createSource(NLMISC::CStringMapper::map(soundName), false); + if (src) + { + src->setLooping(false); + const NLMISC::CVector &pos = _AudioMixer->getListener()->getPos(); + src->setPos(pos); + src->play(); + return src; + } + else + { + nlwarning("Can't play the sound (perhaps it's contextual sound)"); + } return NULL; + } + return NULL; +} + +void CSoundSystem::playAnimation(std::string& name, float lastTime, float curTime, NLSOUND::CSoundContext &context) +{ + if (_AnimManager == NULL) + { + return; + } + + NLSOUND::TSoundAnimId id = _AnimManager->getAnimationFromName(name); + + if (id != NLSOUND::CSoundAnimationNoId) + { + _AnimManager->playAnimation(id, lastTime, curTime, NULL, context); + } +} + +void CSoundSystem::update() +{ + if (_AudioMixer) + { + _AudioMixer->update(); + } +} + +void CSoundSystem::initGraphics() +{ + //H_AUTO2 + nldebug("CSoundSystem::initGraphics"); + + // set particle system sound + NL3D::UParticleSystemSound::setPSSound(_AudioMixer); + + // ... + // todo: displayers for all the test sound sources :) +} + +void CSoundSystem::releaseGraphics() +{ + //H_AUTO2 + nldebug("CSoundSystem::releaseGraphics"); + + // .. + + // clear particle system sound + NL3D::UParticleSystemSound::setPSSound(NULL); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/sound_system.h b/code/nel/tools/3d/object_viewer_qt/src/sound_system.h new file mode 100644 index 000000000..b5022257d --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/sound_system.h @@ -0,0 +1,119 @@ +// NeL - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + + +#ifndef SOUND_SYSTEM_H +#define SOUND_SYSTEM_H + +#include + +// NeL includes +#include +#include +#include +#include + +// STL includes +#include +#include + +namespace NLSOUND +{ + class UAudioMixer; + class USource; +} + +namespace NLMISC +{ + class CMatrix; +} + +namespace NLQT { + +/** +@class CSoundSystem +@brief This class init the sound system used by the object viewer +*/ +class CSoundSystem +{ +public: + CSoundSystem(); + virtual ~CSoundSystem(); + + /// Set the name of the file containing the sample bank + void addSampleBank(const std::string &sampleBankFileName) + { + _SampleBanksFileName.insert(sampleBankFileName); + } + + /// Sets the path which contains samples + void setSamplePath(std::string& path) { _SamplePath = NLMISC::CPath::standardizePath(path, true); } + + /// Sets the path which contains packed sheet + void setPackedSheetPath(std::string& path) { _PackedSheetPath = NLMISC::CPath::standardizePath(path, true); } + + /// Init the sound system this also load the sound bank. See setSoundBank + void init(void); + + /// Release the sound system + void release(void); + + /// Set the listener matrix. + void setListenerMatrix(const NLMISC::CMatrix &m); + + /// Spawn a sound at the user position + void play(const std::string &soundName); + + /// Create a sound at the user position (don't spawn it) + NLSOUND::USource *create(const std::string &soundName); + + /// Load the sound animation with the specified name + void loadAnimation(std::string& name) { _AnimManager->loadAnimation(name); } + + /// Start playing a sound animation. + void playAnimation(std::string& name, float lastTime, float curTime, NLSOUND::CSoundContext &context); + + // Update the sound animations. + //static void updateAnimations(float lastTime, float curTime) { _AnimManager->update(lastTime, curTime); }; + + /// Get the audio mixer, or null if init failed + NLSOUND::UAudioMixer *getAudioMixer(void) { return _AudioMixer; } + + /// Returns a reference to the animation manager + NLSOUND::CSoundAnimManager* getSoundAnimManager() { return _AnimManager; } + + /// Init the particle system sound with the given AudioMixer + void initGraphics(); + + /// Release the particle system sound with the given AudioMixer + void releaseGraphics(); + + /// Update sound. Must be called periodically + void update(); + +private: + NLSOUND::UAudioMixer *_AudioMixer; + std::set _SampleBanksFileName; + NLSOUND::CSoundAnimManager *_AnimManager; + NLMISC::CVector _Zero; + std::string _SamplePath; + std::string _PackedSheetPath; + NLMISC::CMatrix oldMatrix; +}; /* class CSoundSystem */ + +} /* namespace NLQT */ + +#endif // SOUND_SYSTEM_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.cpp new file mode 100644 index 000000000..9915e1fda --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.cpp @@ -0,0 +1,68 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "spinner_dialog.h" + +namespace NLQT { + +CSpinnerDialog::CSpinnerDialog(NL3D::CPSBasisSpinner *sf, CWorkspaceNode *ownerNode, QWidget *parent) + : QDialog(parent) +{ + _verticalLayout = new QVBoxLayout(this); + _nbSamplesLabel = new QLabel(this); + _verticalLayout->addWidget(_nbSamplesLabel); + + _nbSamplesWidget = new NLQT::CEditRangeUIntWidget(this); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(_nbSamplesWidget->sizePolicy().hasHeightForWidth()); + _nbSamplesWidget->setSizePolicy(sizePolicy); + _verticalLayout->addWidget(_nbSamplesWidget); + + _dirWidget = new NLQT::CDirectionWidget(this); + sizePolicy.setHeightForWidth(_dirWidget->sizePolicy().hasHeightForWidth()); + _dirWidget->setSizePolicy(sizePolicy); + _verticalLayout->addWidget(_dirWidget); + + setWindowTitle(tr("Edit spinner")); + _nbSamplesLabel->setText(tr("Nb samples:")); + + _AxisWrapper.OwnerNode = ownerNode; + _NbSampleWrapper.OwnerNode = ownerNode; + _NbSampleWrapper.S = sf; + _AxisWrapper.S = sf; + + _nbSamplesWidget->setRange(1, 512); + _nbSamplesWidget->setWrapper(&_NbSampleWrapper); + _nbSamplesWidget->enableLowerBound(0, true); + _nbSamplesWidget->updateUi(); + + _dirWidget->setWrapper(&_AxisWrapper); + _dirWidget->updateUi(); + + setFixedHeight(sizeHint().height()); +} + +CSpinnerDialog::~CSpinnerDialog() +{ +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.h new file mode 100644 index 000000000..62497b8bc --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/spinner_dialog.h @@ -0,0 +1,73 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SPINNER_DIALOG_H +#define SPINNER_DIALOG_H + +// Qt includes +#include +#include +#include + +// Nel include +#include "nel/3d/ps_plane_basis_maker.h" + +// Project includes +#include "direction_widget.h" +#include "edit_range_widget.h" +#include "ps_wrapper.h" + +namespace NLQT { + +class CSpinnerDialog : public QDialog +{ + Q_OBJECT + +public: + CSpinnerDialog(NL3D::CPSBasisSpinner *sf, CWorkspaceNode *ownerNode, QWidget *parent = 0); + ~CSpinnerDialog(); + +protected: + + /// Wrapper to set the number of samples in the spinner + struct CNbSampleWrapper : public IPSWrapperUInt + { + NL3D::CPSBasisSpinner *S; + uint32 get(void) const { return S->_F.getNumSamples(); } + void set(const uint32 &val) { S->_F.setNumSamples(val); } + } _NbSampleWrapper; + + + /// Wrapper to set the axis of the spinner + struct CAxisWrapper : public IPSWrapper + { + NL3D::CPSBasisSpinner *S; + NLMISC::CVector get(void) const { return S->_F.getAxis(); } + void set(const NLMISC::CVector &axis) { S->_F.setAxis(axis); } + } _AxisWrapper; + + QLabel *_nbSamplesLabel; + QVBoxLayout *_verticalLayout; + CEditRangeUIntWidget *_nbSamplesWidget; + CDirectionWidget *_dirWidget; +}; + +} /* namespace NLQT */ + +#endif // SPINNER_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/stdpch.cpp b/code/nel/tools/3d/object_viewer_qt/src/stdpch.cpp new file mode 100644 index 000000000..eb84ced4d --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/stdpch.cpp @@ -0,0 +1,20 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" diff --git a/code/nel/tools/3d/object_viewer_qt/src/stdpch.h b/code/nel/tools/3d/object_viewer_qt/src/stdpch.h new file mode 100644 index 000000000..4f40c50ae --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/stdpch.h @@ -0,0 +1,176 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef NL_STDPCH_H +#define NL_STDPCH_H + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.cpp new file mode 100644 index 000000000..c3637191e --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.cpp @@ -0,0 +1,71 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "sun_color_dialog.h" + +// NeL includes +#include "nel/3d/u_scene.h" + +// Project includes +#include "modules.h" + +namespace NLQT { + +CSunColorDialog::CSunColorDialog(QWidget *parent) + : QDockWidget(parent) +{ + _ui.setupUi(this); + + connect(this, SIGNAL(visibilityChanged(bool)), this, SLOT(updateSunColor(bool))); + connect(_ui.ambientWidget, SIGNAL(colorChanged(NLMISC::CRGBA)), this, SLOT(setAmbientSunColor(NLMISC::CRGBA))); + connect(_ui.diffuseWidget, SIGNAL(colorChanged(NLMISC::CRGBA)), this, SLOT(setDiffuseSunColor(NLMISC::CRGBA))); + connect(_ui.specularWidget, SIGNAL(colorChanged(NLMISC::CRGBA)), this, SLOT(setSpecularSunColor(NLMISC::CRGBA))); +} + +CSunColorDialog::~CSunColorDialog() +{ +} + +void CSunColorDialog::updateSunColor(bool visible) +{ + if (!visible) + return; + + _ui.ambientWidget->setColor(Modules::objView().getScene()->getSunAmbient()); + _ui.diffuseWidget->setColor(Modules::objView().getScene()->getSunDiffuse()); + _ui.specularWidget->setColor(Modules::objView().getScene()->getSunSpecular()); +} + +void CSunColorDialog::setAmbientSunColor(NLMISC::CRGBA color) +{ + Modules::objView().getScene()->setSunAmbient(color); +} + +void CSunColorDialog::setDiffuseSunColor(NLMISC::CRGBA color) +{ + Modules::objView().getScene()->setSunDiffuse(color); +} + +void CSunColorDialog::setSpecularSunColor(NLMISC::CRGBA color) +{ + Modules::objView().getScene()->setSunSpecular(color); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.h new file mode 100644 index 000000000..790fd8b20 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/sun_color_dialog.h @@ -0,0 +1,58 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SUN_COLOR_DIALOG_H +#define SUN_COLOR_DIALOG_H + +#include "ui_sun_color_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NLQT { + +/** +@class CSunColorDialog +@brief Dialog to choose the sun color in a 3D scene +*/ +class CSunColorDialog: public QDockWidget +{ + Q_OBJECT + +public: + CSunColorDialog(QWidget *parent = 0); + ~CSunColorDialog(); + +private Q_SLOTS: + void updateSunColor(bool visible); + void setAmbientSunColor(NLMISC::CRGBA color); + void setDiffuseSunColor(NLMISC::CRGBA color); + void setSpecularSunColor(NLMISC::CRGBA color); + +private: + + Ui::CSunColorDialog _ui; +}; /* class CSunColorDialog */ + +} /* namespace NLQT */ + +#endif // SUN_COLOR_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/sun_color_form.ui b/code/nel/tools/3d/object_viewer_qt/src/sun_color_form.ui new file mode 100644 index 000000000..d8aadc2b0 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/sun_color_form.ui @@ -0,0 +1,74 @@ + + + CSunColorDialog + + + + 0 + 0 + 120 + 132 + + + + Sun color + + + + + + + Diffuse: + + + + + + + + + + Ambient + + + + + + + + + + Specular: + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + NLQT::CColorEditWidget + QWidget +
color_edit_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/tail_form.ui b/code/nel/tools/3d/object_viewer_qt/src/tail_form.ui new file mode 100644 index 000000000..f6f9e4b19 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/tail_form.ui @@ -0,0 +1,131 @@ + + + CTailParticleWidget + + + + 0 + 0 + 243 + 107 + + + + Form + + + + + + Tail fading + + + + + + + false + + + Tail persist after death + + + + + + + Ribbon orientation + + + + Follow Path + + + + + Follow Path XY + + + + + Identity + + + + + + + + + 72 + 72 + + + + + 72 + 72 + + + + + + + + Tail shape + + + + Triangle + + + + + Quad + + + + + Octogon + + + + + Pentagram + + + + + Simple segment X + + + + + Simple segment Y + + + + + Simple segment Z + + + + + Simple brace + + + + + + + + + NLQT::CGraphicsInfoWidget + QWidget +
graphics_info_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.cpp new file mode 100644 index 000000000..2f4e4ff9f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.cpp @@ -0,0 +1,144 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "tail_particle_widget.h" + +// NeL includes +#include "nel/3d/ps_particle.h" + +// Projects includes + +namespace NLQT { + +CTailParticleWidget::CTailParticleWidget(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.pathWidget->setMode(Mode::RibbonShape); + connect(_ui.tailFadingCheckBox, SIGNAL(toggled(bool)), this, SLOT(setTailFading(bool))); + connect(_ui.tailPersistAfterDeathCheckBox, SIGNAL(toggled(bool)), this, SLOT(setPersistAfterDeath(bool))); + connect(_ui.ribbonOrientationComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setRibbonOrientation(int))); + connect(_ui.tailShapeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setTailShape(int))); +} + +CTailParticleWidget::~CTailParticleWidget() +{ +} + +void CTailParticleWidget::setCurrentTailParticle(CWorkspaceNode *ownerNode, NL3D::CPSTailParticle *tp) +{ + _Node = ownerNode; + + _TailParticle = tp; + + _ui.tailFadingCheckBox->setChecked(tp->getColorFading()); + + if (!dynamic_cast(_TailParticle)) + { + _ui.pathWidget->hide(); + _ui.ribbonOrientationComboBox->hide(); + _ui.tailPersistAfterDeathCheckBox->hide(); + _ui.tailShapeComboBox->hide(); + } + else + { + _ui.pathWidget->show(); + _ui.ribbonOrientationComboBox->show(); + _ui.tailPersistAfterDeathCheckBox->show(); + _ui.tailShapeComboBox->show(); + NL3D::CPSRibbon *r = dynamic_cast(_TailParticle); +// _ui.tailPersistAfterDeathCheckBox = (r->getPersistAfterDeath(); + _ui.ribbonOrientationComboBox->setCurrentIndex(r->getOrientation()); + + // Update graphics widget + std::vector verts; + verts.resize(r->getNbVerticesInShape()); + r->getShape(&verts[0]); + _ui.pathWidget->setRibbonShape(verts, r->getBraceMode()); + } +} + +void CTailParticleWidget::setTailFading(bool state) +{ + if (state != _TailParticle->getColorFading()) + { + _TailParticle->setColorFading(state); + _Node->setModified(true); + } +} + +void CTailParticleWidget::setPersistAfterDeath(bool state) +{ + nlassert(dynamic_cast(_TailParticle)); +// (dynamic_cast(_TailParticle))->setPersistAfterDeath(state); +} + +void CTailParticleWidget::setTailShape(int index) +{ + NL3D::CPSRibbon *r = dynamic_cast(_TailParticle); + nlassert(r); + switch (index) + { + case 0: // triangle + r->setShape(NL3D::CPSRibbon::Triangle, NL3D::CPSRibbon::NbVerticesInTriangle); + break; + case 1: // quad + r->setShape(NL3D::CPSRibbon::Losange, NL3D::CPSRibbon::NbVerticesInLosange); + break; + case 2: // octogon + r->setShape(NL3D::CPSRibbon::HeightSides, NL3D::CPSRibbon::NbVerticesInHeightSide); + break; + case 3: // pentagram + r->setShape(NL3D::CPSRibbon::Pentagram, NL3D::CPSRibbon::NbVerticesInPentagram); + break; + case 4: // simple segment x + r->setShape(NL3D::CPSRibbon::SimpleSegmentX, NL3D::CPSRibbon::NbVerticesInSimpleSegmentX, true); + break; + case 5: // simple segment y + r->setShape(NL3D::CPSRibbon::SimpleSegmentY, NL3D::CPSRibbon::NbVerticesInSimpleSegmentY, true); + break; + case 6: // simple segment z + r->setShape(NL3D::CPSRibbon::SimpleSegmentZ, NL3D::CPSRibbon::NbVerticesInSimpleSegmentZ, true); + break; + case 7: // simple brace + r->setShape(NL3D::CPSRibbon::SimpleBrace, NL3D::CPSRibbon::NbVerticesInSimpleBrace, true); + break; + } + + // Update graphics widget + std::vector verts; + verts.resize(r->getNbVerticesInShape() ); + r->getShape(&verts[0]); + _ui.pathWidget->setRibbonShape(verts, r->getBraceMode()); +} + +void CTailParticleWidget::setRibbonOrientation(int index) +{ + NL3D::CPSRibbon *r = dynamic_cast(_TailParticle); + nlassert(r); + if (index != r->getOrientation()) + { + r->setOrientation((NL3D::CPSRibbon::TOrientation) index); + _Node->setModified(true); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.h b/code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.h new file mode 100644 index 000000000..a5f868cd6 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/tail_particle_widget.h @@ -0,0 +1,66 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef TAIL_PARTICLE_WIDGET_H +#define TAIL_PARTICLE_WIDGET_H + +#include "ui_tail_form.h" + +// STL includes + +// Qt includes + +// NeL includes + +// Project includes +#include "particle_node.h" + +namespace NL3D { + struct CPSTailParticle; +} + +namespace NLQT { + +class CTailParticleWidget: public QWidget +{ + Q_OBJECT + +public: + CTailParticleWidget(QWidget *parent = 0); + ~CTailParticleWidget(); + + void setCurrentTailParticle(CWorkspaceNode *ownerNode, NL3D::CPSTailParticle *tp); + +private Q_SLOTS: + void setTailFading(bool state); + void setPersistAfterDeath(bool state); + void setTailShape(int index); + void setRibbonOrientation(int index); + +private: + CWorkspaceNode *_Node; + + NL3D::CPSTailParticle *_TailParticle; + + Ui::CTailParticleWidget _ui; +}; /* class CTailParticleWidget */ + +} /* namespace NLQT */ + +#endif // TAIL_PARTICLE_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.cpp new file mode 100644 index 000000000..d3bdda106 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.cpp @@ -0,0 +1,70 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "value_blender_dialog.h" + +namespace NLQT { + +CValueBlenderDialog::CValueBlenderDialog(IValueBlenderDialogClient *createInterface, + CWorkspaceNode *ownerNode, + bool destroyInterface, + QWidget *parent) + : QDialog(parent), + _CreateInterface(createInterface), + _Node(ownerNode), + _DestroyInterface(destroyInterface) +{ + _gridLayout = new QGridLayout(this); + _startLabel = new QLabel(this); + + _gridLayout->addWidget(_startLabel, 0, 0, 1, 1); + + _startWidget = _CreateInterface->createDialog(0, _Node, this); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(_startWidget->sizePolicy().hasHeightForWidth()); + _startWidget->setSizePolicy(sizePolicy); + + _gridLayout->addWidget(_startWidget, 1, 0, 1, 1); + + _endLabel = new QLabel(this); + + _gridLayout->addWidget(_endLabel, 2, 0, 1, 1); + + _endWidget = _CreateInterface->createDialog(1, _Node, this); + sizePolicy.setHeightForWidth(_endWidget->sizePolicy().hasHeightForWidth()); + _endWidget->setSizePolicy(sizePolicy); + + _gridLayout->addWidget(_endWidget, 3, 0, 1, 1); + + setWindowTitle(tr("Value blender")); + _startLabel->setText(tr("Start value:")); + _endLabel->setText(tr("End value:")); + + setFixedHeight(sizeHint().height()); +} + +CValueBlenderDialog::~CValueBlenderDialog() +{ + if (_DestroyInterface) delete _CreateInterface; +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.h new file mode 100644 index 000000000..c76d08801 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/value_blender_dialog.h @@ -0,0 +1,200 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VALUE_BLENDER_DIALOG_H +#define VALUE_BLENDER_DIALOG_H + +#include + +// Qt includes +#include +#include +#include +#include +#include + +// STL includes + +// NeL includes +#include +#include +#include + +#include "nel/misc/rgba.h" +#include "nel/3d/ps_attrib_maker.h" + +// Project includes +#include "ps_wrapper.h" +#include "edit_range_widget.h" +#include "color_edit_widget.h" + +namespace NLQT { + +struct IValueBlenderDialogClient +{ + /// Create a dialog to edit a single value. + /// @param index - must be 0 or 1, it says which value is being edited + virtual QWidget *createDialog(uint index, CWorkspaceNode *ownerNode, QWidget *parent = 0) = 0; + + virtual ~IValueBlenderDialogClient() {} +}; + +class CValueBlenderDialog : public QDialog +{ + Q_OBJECT + +public: + /// Create the dialog. + /// @param createInterface - interface that allows to create a dialog to edit one of the 2 values used for the blend. + /// @param destroyInterface - true if this object must take care to call 'delete' on the 'createInterface' pointer + CValueBlenderDialog(IValueBlenderDialogClient *createInterface, + CWorkspaceNode *ownerNode, + bool destroyInterface, + QWidget *parent = 0); + + ~CValueBlenderDialog() ; + +// Implementation +protected: + IValueBlenderDialogClient *_CreateInterface ; + + CWorkspaceNode *_Node; + + bool _DestroyInterface; + + QGridLayout *_gridLayout; + + QLabel *_startLabel; + + QLabel *_endLabel; + + // the 2 dialog used to choose the blending value + QWidget *_startWidget, *_endWidget; +}; + +/// GENERAL INTERFACE FOR BLENDER EDITION + +/// T is the type to be edited (color, float, etc..), even if it is unused +template +class CValueBlenderDialogClientT : public IValueBlenderDialogClient +{ +public: + // the scheme being used. Must be set by the user + NL3D::CPSValueBlendFuncBase *SchemeFunc; + +protected: + virtual QWidget *createDialog(uint index, CWorkspaceNode *ownerNode, QWidget *parent = 0) + { + _ValueInfos[index].ValueIndex = index; + _ValueInfos[index].SchemeFunc = SchemeFunc; + _ValueInfos[index].OwnerNode = ownerNode; + return newDialog(&_ValueInfos[index], parent); + } + + + // construct a dialog + virtual QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) = 0; + + // inherited from IPSWrapper + struct COneValueInfo : public IPSWrapper + { + // value 0 or 1 being edited + uint ValueIndex; + // the scheme being edited + NL3D::CPSValueBlendFuncBase *SchemeFunc; + + virtual T get(void) const + { + T t1, t2; + SchemeFunc->getValues(t1, t2); + return ValueIndex == 0 ? t1 : t2; + } + virtual void set(const T &value) + { + T t1, t2; + SchemeFunc->getValues(t1, t2); + if (ValueIndex == 0 ) t1 = value; else t2 = value; + SchemeFunc->setValues(t1, t2); + } + }; + + COneValueInfo _ValueInfos[2]; +}; + +/// FLOAT BLENDER EDITION INTERFACE +class CFloatBlenderDialogClient : public CValueBlenderDialogClientT +{ +public: + QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) + { + CEditRangeFloatWidget *erf = new CEditRangeFloatWidget(parent); + erf->setRange(MinRange, MaxRange); + erf->setWrapper(wrapper); + erf->updateUi(); + return erf; + } + float MinRange, MaxRange; +}; + +/// UINT BLENDER EDITION INTERFACE +class CUIntBlenderDialogClient : public CValueBlenderDialogClientT +{ +public: + QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) + { + CEditRangeUIntWidget *erf = new CEditRangeUIntWidget(parent); + erf->setRange(MinRange, MaxRange); + erf->setWrapper(wrapper); + erf->updateUi(); + return erf; + } + uint32 MinRange, MaxRange; +}; + +/// INT BLENDER EDITION INTERFACE +class CIntBlenderDialogClient : public CValueBlenderDialogClientT +{ +public: + QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) + { + CEditRangeIntWidget *erf = new CEditRangeIntWidget(parent); + erf->setRange(MinRange, MaxRange); + erf->setWrapper(wrapper); + erf->updateUi(); + return erf; + } + sint32 MinRange, MaxRange; +}; + +/// RGBA BLENDER EDITION INTERFACE +class CRGBABlenderDialogClient : public CValueBlenderDialogClientT +{ +public: + QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) + { + CColorEditWidget *ce = new CColorEditWidget(parent); + ce->setWrapper(wrapper); + ce->updateUi(); + return ce; + } +}; + +} /* namespace NLQT */ + +#endif // VALUE_BLENDER_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.cpp new file mode 100644 index 000000000..e44e6f393 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.cpp @@ -0,0 +1,41 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "value_from_emitter_dialog.h" + +namespace NLQT { + +CValueFromEmitterDialog::CValueFromEmitterDialog(QWidget *widget, QWidget *parent) + : QDialog(parent) +{ + _gridLayout = new QGridLayout(this); + _widget = widget; + _gridLayout->addWidget(_widget, 0, 0, 1, 1); + + setWindowTitle(tr("Value from emitter")); + + //setFixedHeight(sizeHint().height()); +} + +CValueFromEmitterDialog::~CValueFromEmitterDialog() +{ +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.h new file mode 100644 index 000000000..d40dce655 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/value_from_emitter_dialog.h @@ -0,0 +1,109 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VALUE_FROM_EMITTER_DIALOG_H +#define VALUE_FROM_EMITTER_DIALOG_H + +#include + +// Qt includes +#include +#include +#include +#include + +// NeL include +#include "nel/3d/ps_attrib_maker.h" + +// Project includes +#include "ps_wrapper.h" +#include "attrib_widget.h" + +namespace NL3D +{ + template class CPSAttribMakerMemory ; +} + +namespace NLQT { + +class CValueFromEmitterDialog : public QDialog +{ + Q_OBJECT + +public: + CValueFromEmitterDialog(QWidget *widget, QWidget *parent = 0); + ~CValueFromEmitterDialog(); + + virtual void init() = 0; + +protected: + + QGridLayout *_gridLayout; + QWidget *_widget; +}; + +/** construct a dialog that allow to edit a scheme used for initial attribute generation in a particle + */ +template class CValueFromEmitterDialogT : public CValueFromEmitterDialog +{ +public: + CValueFromEmitterDialogT(NL3D::CPSAttribMakerMemory *editedScheme, CAttribWidgetT *srcDlg, QWidget *parent = 0) + : CValueFromEmitterDialog(srcDlg, parent), _AttrbDlg(srcDlg) + { + nlassert(srcDlg); + _SchemeWrapper.S = editedScheme ; + } + // inherited from CValueFromEmitterDialog + void init() + { + _AttrbDlg->setEnabledConstantValue(false) ; + _AttrbDlg->setWrapper(&_DummyWrapper) ; + _AttrbDlg->setSchemeWrapper(&_SchemeWrapper) ; + _AttrbDlg->init(); + } + + ~CValueFromEmitterDialogT() + { + delete _AttrbDlg ; + } + +protected: + + /// the dialog that allow us to edit the scheme + CAttribWidgetT *_AttrbDlg ; + + /// a wrapper to edit the scheme (which himself owns a scheme !!) + struct CSchemeWrapper : public IPSSchemeWrapper + { + NL3D::CPSAttribMakerMemory *S ; + virtual NL3D::CPSAttribMaker *getScheme(void) const { return S->getScheme() ; } + virtual void setScheme(NL3D::CPSAttribMaker *s) { S->setScheme(s) ; } ; + } _SchemeWrapper ; + + /// a dummy wrapper for constant value. This shouldn't be called , however + struct CDummyWrapper : public IPSWrapper + { + T get(void) const { nlassert(false) ; return T() ; } + void set(const T &) { nlassert(false) ; } + } _DummyWrapper ; +} ; + +} /* namespace NLQT */ + +#endif // VALUE_FROM_EMITTER_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.cpp new file mode 100644 index 000000000..02e4ae1d7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.cpp @@ -0,0 +1,395 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "value_gradient_dialog.h" + +// Qt include +#include + +// NeL includes +#include +#include "nel/3d/texture_file.h" +#include "nel/misc/path.h" + +// Projects include +#include "particle_texture_widget.h" + +namespace NLQT { + +CGradientDialog::CGradientDialog(CWorkspaceNode *ownerNode, + IValueGradientClient *clientInterface, + bool destroyClientInterface, + bool canTuneNbStages, + uint minSize, + QWidget *parent) + : QDialog(parent), + _MinSize(minSize), + _CanTuneNbStages(canTuneNbStages), + _ClientInterface(clientInterface), + _DestroyClientInterface(destroyClientInterface), + _Node(ownerNode) +{ + nlassert(_ClientInterface); + + resize(490, 210); + _gridLayout = new QGridLayout(this); + _listWidget = new QListWidget(this); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(_listWidget->sizePolicy().hasHeightForWidth()); + _listWidget->setSizePolicy(sizePolicy); + //_listWidget->setIconSize(QSize(16, 16)); + _listWidget->setMaximumSize(QSize(175, 16777215)); + _gridLayout->addWidget(_listWidget, 0, 0, 9, 1); + + _horizontalLayout = new QHBoxLayout(); + _addPushButton = new QPushButton(this); + QSizePolicy sizePolicy1(QSizePolicy::Fixed, QSizePolicy::Fixed); + sizePolicy1.setHorizontalStretch(0); + sizePolicy1.setVerticalStretch(0); + sizePolicy1.setHeightForWidth(_addPushButton->sizePolicy().hasHeightForWidth()); + _addPushButton->setSizePolicy(sizePolicy1); + _addPushButton->setMaximumSize(QSize(36, 36)); + QIcon icon; + icon.addFile(QString::fromUtf8(":/images/list-add.png"), QSize(), QIcon::Normal, QIcon::Off); + _addPushButton->setIcon(icon); + _addPushButton->setIconSize(QSize(24, 24)); + _horizontalLayout->addWidget(_addPushButton); + + _removePushButton = new QPushButton(this); + sizePolicy1.setHeightForWidth(_removePushButton->sizePolicy().hasHeightForWidth()); + _removePushButton->setSizePolicy(sizePolicy1); + _removePushButton->setMinimumSize(QSize(0, 0)); + _removePushButton->setMaximumSize(QSize(36, 36)); + QIcon icon1; + icon1.addFile(QString::fromUtf8(":/images/list-remove.png"), QSize(), QIcon::Normal, QIcon::Off); + _removePushButton->setIcon(icon1); + _removePushButton->setIconSize(QSize(24, 24)); + _horizontalLayout->addWidget(_removePushButton); + + _upPushButton = new QPushButton(this); + sizePolicy1.setHeightForWidth(_upPushButton->sizePolicy().hasHeightForWidth()); + _upPushButton->setSizePolicy(sizePolicy1); + _upPushButton->setMaximumSize(QSize(36, 36)); + QIcon icon2; + icon2.addFile(QString::fromUtf8(":/images/go-up.png"), QSize(), QIcon::Normal, QIcon::Off); + _upPushButton->setIcon(icon2); + _upPushButton->setIconSize(QSize(24, 24)); + _horizontalLayout->addWidget(_upPushButton); + + _downPushButton = new QPushButton(this); + sizePolicy1.setHeightForWidth(_downPushButton->sizePolicy().hasHeightForWidth()); + _downPushButton->setSizePolicy(sizePolicy1); + _downPushButton->setMaximumSize(QSize(36, 36)); + QIcon icon3; + icon3.addFile(QString::fromUtf8(":/images/go-down.png"), QSize(), QIcon::Normal, QIcon::Off); + _downPushButton->setIcon(icon3); + _downPushButton->setIconSize(QSize(24, 24)); + _horizontalLayout->addWidget(_downPushButton); + + _horizontalSpacer = new QSpacerItem(208, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + _horizontalLayout->addItem(_horizontalSpacer); + _gridLayout->addLayout(_horizontalLayout, 0, 1, 1, 2); + + _label = new QLabel(this); + _gridLayout->addWidget(_label, 1, 1, 1, 1); + + _nbStepWidget = new NLQT::CEditRangeUIntWidget(this); + _gridLayout->addWidget(_nbStepWidget, 2, 1, 1, 2); + + _verticalSpacer = new QSpacerItem(20, 85, QSizePolicy::Minimum, QSizePolicy::Expanding); + _gridLayout->addItem(_verticalSpacer, 3, 2, 1, 1); + + editWidget = _ClientInterface->createDialog(this); + _gridLayout->addWidget(editWidget, 4, 1, 1, 2); + + setWindowTitle(tr("Dialog")); + _label->setText(tr("Num samples:")); + + if (canTuneNbStages) + { + _NbStepWrapper.OwnerNode = _Node; + _NbStepWrapper.I = _ClientInterface; + _nbStepWidget->setRange(1, 255); + _nbStepWidget->enableLowerBound(0, true); + _nbStepWidget->setWrapper(&_NbStepWrapper); + _nbStepWidget->updateUi(); + } + else + { + _nbStepWidget->hide(); + _label->hide(); + } + + connect(_addPushButton, SIGNAL(clicked()), this, SLOT(addValue())); + connect(_removePushButton, SIGNAL(clicked()), this, SLOT(removeValue())); + connect(_upPushButton, SIGNAL(clicked()), this, SLOT(valueUp())); + connect(_downPushButton, SIGNAL(clicked()), this, SLOT(valueDown())); + connect(_listWidget, SIGNAL(currentRowChanged(int)), this, SLOT(changeCurrentRow(int))); + connect(clientInterface, SIGNAL(itemChanged()), this, SLOT(updateItem())); + + _Size = _ClientInterface->getSchemeSize(); + for (uint k = 0; k < _Size; ++k) + { + QListWidgetItem *item = new QListWidgetItem(); + _ClientInterface->displayValue(k, item); + _listWidget->addItem(item); + } + _removePushButton->setEnabled(_Size > _MinSize ? true : false); + _listWidget->setCurrentRow(0); +} + +CGradientDialog::~CGradientDialog() +{ + if (_DestroyClientInterface) delete _ClientInterface; +} + +void CGradientDialog::addValue() +{ + nlassert(_ClientInterface); + if (!_ClientInterface->modifyGradient(IValueGradientClient::Add, 0)) return; + ++_Size; + QListWidgetItem *item = new QListWidgetItem(); + _ClientInterface->displayValue(_Size - 1, item); + _listWidget->addItem(item); + + _removePushButton->setEnabled(true); + + _listWidget->setCurrentRow(_Size - 1); +} + +void CGradientDialog::insertValue() +{ + nlassert(_ClientInterface); + int oldIndex = _listWidget->currentRow(); + if (!_ClientInterface->modifyGradient(IValueGradientClient::Insert, oldIndex)) return; + ++_Size; + QListWidgetItem *item = new QListWidgetItem(); + _ClientInterface->displayValue(_Size - 1, item); + _listWidget->insertItem(oldIndex, item); + _listWidget->setCurrentRow(oldIndex); +} + +void CGradientDialog::removeValue() +{ + nlassert(_ClientInterface); + + int oldIndex = _listWidget->currentRow(); + if (oldIndex == -1) + return; + + if (uint(oldIndex) == 0) + _listWidget->setCurrentRow(oldIndex + 1); + else + _listWidget->setCurrentRow(oldIndex - 1); + + if (!_ClientInterface->modifyGradient(IValueGradientClient::Delete, oldIndex)) + { + _listWidget->setCurrentRow(oldIndex); + return; + } + + --_Size; + + if (_Size <= _MinSize) + _removePushButton->setEnabled(false); + + QListWidgetItem *removeItem = _listWidget->takeItem(oldIndex); + if (!removeItem) + delete removeItem; +} + +void CGradientDialog::valueDown() +{ + nlassert(_ClientInterface); + int currentRow = _listWidget->currentRow(); + if (!((currentRow == _listWidget->count()-1) || (currentRow == -1))) + { + if (!_ClientInterface->modifyGradient(IValueGradientClient::Down, currentRow)) return; + QListWidgetItem *item = _listWidget->takeItem(currentRow); + _listWidget->insertItem(++currentRow, item); + _listWidget->setCurrentRow(currentRow); + } + _listWidget->setCurrentRow(currentRow); +} + +void CGradientDialog::valueUp() +{ + nlassert(_ClientInterface); + int currentRow = _listWidget->currentRow(); + if (!((currentRow == 0) || (currentRow == -1))) + { + if (!_ClientInterface->modifyGradient(IValueGradientClient::Up, currentRow)) return; + QListWidgetItem *item = _listWidget->takeItem(currentRow); + _listWidget->insertItem(--currentRow, item); + _listWidget->setCurrentRow(currentRow); + } + _listWidget->setCurrentRow(currentRow); +} + +void CGradientDialog::changeCurrentRow(int currentRow) +{ + _ClientInterface->setCurrentIndex(currentRow); +} + +void CGradientDialog::updateItem() +{ + _ClientInterface->displayValue(_listWidget->currentRow(), _listWidget->currentItem()); +} + +QWidget *CTextureGradientInterface::createDialog(QWidget *parent) +{ + editWidget = new CParticleTextureWidget(); + + _TextureWrapper.P = TP; + _TextureWrapper.Index = 0; + _TextureWrapper.OwnerNode = Node; + editWidget->setWrapper(&_TextureWrapper); + connect(editWidget, SIGNAL(textureChanged(QString)), this, SIGNAL(itemChanged())); + return editWidget; +} + +bool CTextureGradientInterface::modifyGradient(TAction action, uint index) +{ + nlassert(TP); + nlassert(TP->getTextureGroup()); + + NLMISC::CSmartPtr tex = TP->getTextureGroup()->getTexture(index); + std::string texName = (static_cast(tex.getPtr()))->getFileName().c_str(); + if (texName.empty()) + return false; + + std::vector< NLMISC::CSmartPtr > textureList; + textureList.resize(TP->getTextureGroup()->getNbTextures()); + TP->getTextureGroup()->getTextures(&textureList[0]); + + switch(action) + { + case IValueGradientClient::Add: + { + // we duplicate the last texture, so that they have the same size + NLMISC::CSmartPtr lastTex = textureList[textureList.size() - 1]; + textureList.push_back(lastTex); + } + break; + case IValueGradientClient::Insert: + { + // we duplicate the current texture, so that they have the same size + NLMISC::CSmartPtr tex = textureList[index]; + textureList.insert(textureList.begin() + index, tex); + } + break; + case IValueGradientClient::Delete: + textureList.erase(textureList.begin() + index); + break; + case IValueGradientClient::Up: + return false; + case IValueGradientClient::Down: + return false; + } + + TP->getTextureGroup()->setTextures(&textureList[0], (uint)textureList.size()); + return true; +} + +void CTextureGradientInterface::displayValue(uint index, QListWidgetItem *item) +{ + QPixmap pixmap; + NLMISC::CSmartPtr tex = TP->getTextureGroup()->getTexture(index); + + if (dynamic_cast(tex.getPtr())) + { + std::string texName = (static_cast(tex.getPtr()))->getFileName().c_str(); + if (!texName.empty()) + { + pixmap.load(NLMISC::CPath::lookup(texName).c_str()); + item->setText(texName.c_str()); + } + else + item->setText("Dummy texture"); + } + item->setIcon(QIcon(pixmap)); +} + +void CTextureGradientInterface::setCurrentIndex(uint index) +{ + _TextureWrapper.Index = index; + editWidget->updateUi(); +} + +uint32 CTextureGradientInterface::getSchemeSize(void) const +{ + nlassert(TP->getTextureGroup()); + return TP->getTextureGroup()->getNbTextures(); +} +uint32 CTextureGradientInterface::getNbSteps(void) const +{ + return 1; +} +void CTextureGradientInterface::setNbSteps(uint32 value) +{ + // this should never be called, as we don't allow nbsteps to be called + nlassert(false); +} + +NL3D::ITexture *CTextureGradientInterface::CTextureWrapper::get(void) +{ + nlassert(P); + nlassert(P->getTextureGroup()); + return P->getTextureGroup()->getTexture(Index); +} + +void CTextureGradientInterface::CTextureWrapper::set(NL3D::ITexture *t) +{ + nlassert(P); + nlassert(P->getTextureGroup()); + + // if a texture is added, it must have the same size than other textures + if (P->getTextureGroup()->getNbTextures() > 1) + { + NLMISC::CSmartPtr tex = P->getTextureGroup()->getTexture(0); + tex->generate(); + t->generate(); + + if (t->getWidth() != tex->getWidth() || t->getHeight() != tex->getHeight()) + { + QMessageBox::critical(0, QString("Texture error"), QString("All textures must have the same size !"), QMessageBox::Ok); + return; + } + + if (t->PixelFormat != tex->PixelFormat) + { + QMessageBox::critical(0, QString("Texture error"), QString("All textures must have the same pixel format !"), QMessageBox::Ok); + return; + } + } + + std::vector< NLMISC::CSmartPtr > textureList; + textureList.resize(P->getTextureGroup()->getNbTextures()); + P->getTextureGroup()->getTextures(&textureList[0]); + + textureList[Index] = t; + + + P->getTextureGroup()->setTextures(&textureList[0], (uint)textureList.size()); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.h new file mode 100644 index 000000000..5d109857d --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/value_gradient_dialog.h @@ -0,0 +1,444 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VALUE_GRADIENT_DIALOG_H +#define VALUE_GRADIENT_DIALOG_H + +#include + +// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NeL includes +#include + +// Project includes +#include "basic_edit_widget.h" +#include "color_edit_widget.h" +#include "edit_range_widget.h" +#include "ps_wrapper.h" +#include "particle_node.h" + +namespace NL3D { + class CPSTexturedParticle; +} + +namespace NLQT { + +class CParticleTextureWidget; + +class IValueGradientClient: public QObject +{ + Q_OBJECT +public: + IValueGradientClient(QObject *parent = 0): QObject(parent) {} + + virtual QWidget *createDialog(QWidget *parent) = 0; + + /// This enumerate the action that we can apply on a gradient + enum TAction { Add, Insert, Delete, Up, Down }; + + /// A function that can display a value in a gradient, with the given offset + virtual void displayValue(uint index, QListWidgetItem *item) = 0; + + virtual void setCurrentIndex(uint index) = 0; + + /// A function that can add, remove, or insert a new element in the gradient + virtual bool modifyGradient(TAction, uint index) = 0; + + /// Return the number of values in a scheme + virtual uint32 getSchemeSize(void) const = 0; + + /// Get the number of interpolation step + virtual uint32 getNbSteps(void) const = 0; + + /// Set the number of interpolation steps + virtual void setNbSteps(uint32 value) = 0; + +Q_SIGNALS: + void itemChanged(); +}; + +class CGradientDialog: public QDialog +{ + Q_OBJECT + +public: + CGradientDialog(CWorkspaceNode *ownerNode, + IValueGradientClient *clientInterface, + bool destroyClientInterface, + bool canTuneNbStages = true, + uint minSize = 2, + QWidget *parent = 0); + ~CGradientDialog(); + +private Q_SLOTS: + void addValue(); + void insertValue(); + void removeValue(); + void valueDown(); + void valueUp(); + void changeCurrentRow(int currentRow); + void updateItem(); + +protected: + + // the minimum number of element in the gradient + uint _MinSize; + + // false to disable the dialog that control the number of stages between each value + bool _CanTuneNbStages; + + IValueGradientClient *_ClientInterface; + + bool _DestroyClientInterface; + + // the current size of the gradient + uint _Size; + + CWorkspaceNode *_Node; + + // a wrapper to tune the number of step + struct CNbStepWrapper :public IPSWrapperUInt + { + // the interface that was passed to the dialog this struct is part of + IValueGradientClient *I; + uint32 get(void) const { return I->getNbSteps(); } + void set(const uint32 &nbSteps) { I->setNbSteps(nbSteps); } + + } _NbStepWrapper; + + QGridLayout *_gridLayout; + QListWidget *_listWidget; + QHBoxLayout *_horizontalLayout; + QPushButton *_addPushButton; + QPushButton *_removePushButton; + QPushButton *_upPushButton; + QPushButton *_downPushButton; + QSpacerItem *_horizontalSpacer; + QLabel *_label; + NLQT::CEditRangeUIntWidget *_nbStepWidget; + QSpacerItem *_verticalSpacer; + QWidget *editWidget; +}; /* class CGradientDialog */ + +/** +@class CValueGradientClientT +@brief This template generate an interface that is used with the gradient edition dialog +This the type to be edited (color, floet, etc..) + */ +template +class CValueGradientClientT : public IValueGradientClient, public IPSWrapper +{ +public: + CValueGradientClientT(QObject *parent = 0): IValueGradientClient(parent) {} + + /// the gradient being edited, must be filled by the instancier + NL3D::CPSValueGradientFunc *Scheme; + + /// the gradient dialog, must be filled by the instancier + T DefaultValue; + + /// inherited from IPSWrapper + virtual T get(void) const { return Scheme->getValue(_CurrentEditedIndex); } + virtual void set(const T &v) + { + T *tab = new T[Scheme->getNumValues()]; + Scheme->getValues(tab); + tab[_CurrentEditedIndex] = v; + Scheme->setValues(tab, Scheme->getNumValues(), Scheme->getNumStages()); + delete[] tab; + } + + virtual QWidget *createDialog(QWidget *parent) + { + return newDialog(this, parent); + } + + /// create a new dialog with given id and wrapper + virtual QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) = 0; + + virtual void setCurrentIndex(uint index) = 0; + + virtual void displayValue(uint index, QListWidgetItem *item) = 0; + + /// a function that can add, remove, or insert a new element in the gradient + virtual bool modifyGradient(TAction action, uint index) + { + + T *tab = new T[Scheme->getNumValues() + 1]; // +1 is for the add / insert case + Scheme->getValues(tab); + + switch(action) + { + case IValueGradientClient::Add: + tab[Scheme->getNumValues()] = DefaultValue; + Scheme->setValues(tab, Scheme->getNumValues() + 1, Scheme->getNumStages()); + break; + case IValueGradientClient::Insert: + ::memmove(tab + (index + 1), tab + index, sizeof(T) * (Scheme->getNumValues() - index)); + tab[index] = DefaultValue; + Scheme->setValues(tab, Scheme->getNumValues() + 1, Scheme->getNumStages()); + break; + case IValueGradientClient::Delete: + ::memmove(tab + index, tab + index + 1, sizeof(T) * (Scheme->getNumValues() - index - 1)); + Scheme->setValues(tab, Scheme->getNumValues() - 1, Scheme->getNumStages()); + break; + case IValueGradientClient::Up: + nlassert(index > 0); + std::swap(tab[index], tab[index - 1]); + Scheme->setValues(tab, Scheme->getNumValues(), Scheme->getNumStages()); + break; + case IValueGradientClient::Down: + nlassert(index < Scheme->getNumValues() - 1); + std::swap(tab[index], tab[index + 1]); + Scheme->setValues(tab, Scheme->getNumValues(), Scheme->getNumStages()); + break; + } + + delete[] tab; + return true; + } + virtual uint32 getSchemeSize(void) const { return Scheme->getNumValues(); } + + /// Get the number of interpolation step + uint32 getNbSteps(void) const + { + return Scheme->getNumStages(); + } + + /// Set the number of interpolation steps + void setNbSteps(uint32 value) + { + Scheme->setNumStages(value); + } + +protected: + // index of the value OF the current dialog that exist + uint32 _CurrentEditedIndex; +}; /* class CValueGradientClientT */ + +/// FLOAT GRADIENT EDITION INTERFACE +class CFloatGradientWrapper : public CValueGradientClientT +{ + Q_OBJECT +public: + CFloatGradientWrapper(QObject *parent = 0): CValueGradientClientT(parent) {} + + virtual QWidget *newDialog(IPSWrapperFloat *wrapper, QWidget *parent) + { + editWidget = new CEditRangeFloatWidget(parent); + editWidget->setRange(MinRange, MaxRange); + editWidget->setWrapper(wrapper); + connect(editWidget, SIGNAL(valueChanged(float)), this, SIGNAL(itemChanged())); + return editWidget; + } + + virtual void setCurrentIndex(uint index) + { + _CurrentEditedIndex = index; + editWidget->updateUi(); + } + + virtual void displayValue(uint index, QListWidgetItem *item) + { + item->setText(QString("%1").arg(Scheme->getValue(index))); + } + + CEditRangeFloatWidget *editWidget; + float MinRange, MaxRange; +}; /* CFloatGradientWrapper */ + +/// UINT GRADIENT EDITION INTERFACE +class CUIntGradientWrapper : public CValueGradientClientT +{ + Q_OBJECT +public: + CUIntGradientWrapper(QObject *parent = 0): CValueGradientClientT(parent) {} + + virtual QWidget *newDialog(IPSWrapperUInt *wrapper, QWidget *parent) + { + editWidget = new CEditRangeUIntWidget(parent); + editWidget->setRange(MinRange, MaxRange); + editWidget->setWrapper(wrapper); + connect(editWidget, SIGNAL(valueChanged(uint32)), this, SIGNAL(itemChanged())); + return editWidget; + } + + virtual void setCurrentIndex(uint index) + { + _CurrentEditedIndex = index; + editWidget->updateUi(); + } + + virtual void displayValue(uint index, QListWidgetItem *item) + { + item->setText(QString("%1").arg(Scheme->getValue(index))); + } + + CEditRangeUIntWidget *editWidget; + uint32 MinRange, MaxRange; +}; /* CUIntGradientWrapper */ + +/// INT GRADIENT EDITION INTERFACE +class CIntGradientWrapper : public CValueGradientClientT +{ + Q_OBJECT +public: + CIntGradientWrapper(QObject *parent = 0): CValueGradientClientT(parent) {} + + virtual QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) + { + editWidget = new CEditRangeIntWidget(parent); + editWidget->setRange(MinRange, MaxRange); + editWidget->setWrapper(wrapper); + connect(editWidget, SIGNAL(valueChanged(sint32)), this, SIGNAL(itemChanged())); + return editWidget; + } + + virtual void setCurrentIndex(uint index) + { + _CurrentEditedIndex = index; + editWidget->updateUi(); + } + + virtual void displayValue(uint index, QListWidgetItem *item) + { + item->setText(QString("%1").arg(Scheme->getValue(index))); + } + + CEditRangeIntWidget *editWidget; + sint32 MinRange, MaxRange; +}; /* CIntGradientWrapper */ + +/// COLOR GRADIENT EDITION INTERFACE +class CColorGradientWrapper : public CValueGradientClientT +{ + Q_OBJECT +public: + CColorGradientWrapper(QObject *parent = 0): CValueGradientClientT(parent) {} + + virtual QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) + { + editWidget = new CColorEditWidget(parent); + editWidget->setWrapper(wrapper); + connect(editWidget, SIGNAL(colorChanged(NLMISC::CRGBA)), this, SIGNAL(itemChanged())); + return editWidget; + } + + virtual void setCurrentIndex(uint index) + { + _CurrentEditedIndex = index; + editWidget->updateUi(); + } + + virtual void displayValue(uint index, QListWidgetItem *item) + { + NLMISC::CRGBA color = Scheme->getValue(index); + item->setText(QString("RGBA(%1,%2,%3)").arg(color.R).arg(color.G).arg(color.B)); + QPixmap pixmap(QSize(16, 16)); + QPainter painter(&pixmap); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(QBrush(QColor(color.R, color.G, color.B))); + painter.setPen(QPen(Qt::black, 1, Qt::SolidLine)); + painter.drawRect(0, 0, pixmap.width() , pixmap.height()); + item->setIcon(QIcon(pixmap)); + } + + CColorEditWidget *editWidget; +}; /* CColorGradientWrapper */ + +/// PLANE BASIS GRADIENT EDITION INTERFACE +class CPlaneBasisGradientWrapper : public CValueGradientClientT +{ + Q_OBJECT +public: + CPlaneBasisGradientWrapper(QObject *parent = 0): CValueGradientClientT(parent) {} + + virtual QWidget *newDialog(IPSWrapper *wrapper, QWidget *parent) + { + editWidget = new CBasicEditWidget(parent); + editWidget->setWrapper(wrapper); + return editWidget; + } + + virtual void setCurrentIndex(uint index) + { + _CurrentEditedIndex = index; + editWidget->updateUi(); + } + + virtual void displayValue(uint index, QListWidgetItem *item) + { + item->setText(QString("Plane %1").arg(index)); + } + + CBasicEditWidget *editWidget; +}; /* class CPlaneBasisGradientWrapper */ + +/** +@class CTextureGradientInterface +@brief The implementation of this struct tells the gradient dialog bow how to edit a texture list +*/ +class CTextureGradientInterface : public IValueGradientClient +{ + Q_OBJECT +public: + CTextureGradientInterface(QObject *parent = 0): IValueGradientClient(parent) {} + + CTextureGradientInterface(NL3D::CPSTexturedParticle *tp, CWorkspaceNode *ownerNode): Node(ownerNode), TP(tp) {} + + CWorkspaceNode *Node; + NL3D::CPSTexturedParticle *TP; + + // all method inherited from IValueGradientClient + virtual QWidget *createDialog(QWidget *parent); + virtual bool modifyGradient(TAction, uint index); + virtual void displayValue(uint index, QListWidgetItem *item); + virtual void setCurrentIndex(uint index); + virtual uint32 getSchemeSize(void) const; + virtual uint32 getNbSteps(void) const; + virtual void setNbSteps(uint32 value); + + /// wrapper for the texture chooser + /// that allows to choose a texture in the list + struct CTextureWrapper : public IPSWrapperTexture + { + NL3D::CPSTexturedParticle *P; + // index of the particle in the list + uint32 Index; + NL3D::ITexture *get(void); + void set(NL3D::ITexture *t); + } _TextureWrapper; + + CParticleTextureWidget *editWidget; +}; + +} /* namespace NLQT */ + +#endif // VALUE_GRADIENT_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.cpp new file mode 100644 index 000000000..dfa8d3e92 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.cpp @@ -0,0 +1,235 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_appearance_page.h" + +// Qt includes +#include +#include +#include + +// NeL includes +#include + +// Projects include +#include "modules.h" + +namespace NLQT { + +CVegetableApperancePage::CVegetableApperancePage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.bendPhaseGroupBox->setDefaultRangeAbs(NL_VEGETABLE_BENDPHASE_RANGE_MIN, NL_VEGETABLE_BENDPHASE_RANGE_MAX); + _ui.bendPhaseGroupBox->setDefaultRangeRand(NL_VEGETABLE_BENDPHASE_RANGE_MIN, NL_VEGETABLE_BENDPHASE_RANGE_MAX); + _ui.bendPhaseGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + _ui.bendFactorGroupBox->setDefaultRangeAbs(NL_VEGETABLE_BENDFACTOR_RANGE_MIN, NL_VEGETABLE_BENDFACTOR_RANGE_MAX); + _ui.bendFactorGroupBox->setDefaultRangeRand(NL_VEGETABLE_BENDFACTOR_RANGE_MIN, NL_VEGETABLE_BENDFACTOR_RANGE_MAX); + _ui.bendFactorGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + _ui.colorNoiseGroupBox->setDefaultRangeAbs(NL_VEGETABLE_COLOR_RANGE_MIN, NL_VEGETABLE_COLOR_RANGE_MAX); + _ui.colorNoiseGroupBox->setDefaultRangeRand(NL_VEGETABLE_COLOR_RANGE_MIN, NL_VEGETABLE_COLOR_RANGE_MAX); + _ui.colorNoiseGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + connect(_ui.bendFactorGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueBendFactor(NLMISC::CNoiseValue))); + connect(_ui.bendPhaseGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueBendPhase(NLMISC::CNoiseValue))); + connect(_ui.colorNoiseGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueColor(NLMISC::CNoiseValue))); + + connect(_ui.addColorPushButton, SIGNAL(clicked()), this, SLOT(addNewColor())); + connect(_ui.insColorPushButton, SIGNAL(clicked()), this, SLOT(insNewColor())); + connect(_ui.removePushButton, SIGNAL(clicked()), this, SLOT(removeColor())); + connect(_ui.getListPushButton, SIGNAL(clicked()), this, SLOT(getFromListColors())); + + connect(_ui.listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(browseColor(QListWidgetItem*))); + setEnabled(false); +} + +CVegetableApperancePage::~CVegetableApperancePage() +{ +} + +QIcon getRectColorIcon(QColor color) +{ + QPixmap pixmap(QSize(32, 32)); + QPainter painter(&pixmap); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(QBrush(color)); + painter.setPen(QPen(Qt::black, 1, Qt::SolidLine)); + painter.drawRect(0, 0, pixmap.width() , pixmap.height()); + return QIcon(pixmap); +} + +void CVegetableApperancePage::setVegetableToEdit(NL3D::CVegetable *vegetable) +{ + _Vegetable = vegetable; + + if(_Vegetable) + { + setEnabled(true); + _ui.bendPhaseGroupBox->setNoiseValue(_Vegetable->BendPhase, false); + _ui.bendFactorGroupBox->setNoiseValue(_Vegetable->BendFactor, false); + _ui.colorNoiseGroupBox->setNoiseValue(_Vegetable->Color.NoiseValue, false); + + updateColorList(); + } + else + setEnabled(false); +} + +void CVegetableApperancePage::setNoiseValueBendPhase(NLMISC::CNoiseValue value) +{ + _Vegetable->BendPhase = value; + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableApperancePage::setNoiseValueBendFactor(NLMISC::CNoiseValue value) +{ + _Vegetable->BendFactor = value; + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableApperancePage::setNoiseValueColor(NLMISC::CNoiseValue value) +{ + _Vegetable->Color.NoiseValue = value; + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableApperancePage::browseColor(QListWidgetItem * item) +{ + sint row = _ui.listWidget->currentRow(); + + NLMISC::CRGBA oldColor = _Vegetable->Color.Gradients[row]; + QColor color = QColorDialog::getColor(QColor(oldColor.R, oldColor.G, oldColor.B)); + if (!color.isValid()) return; + + item->setIcon(getRectColorIcon(color)); + + _Vegetable->Color.Gradients[row] = NLMISC::CRGBA(color.red(), color.green(), color.blue()); + + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableApperancePage::addNewColor() +{ + sint row = _ui.listWidget->currentRow(); + + // copy the current selected color + NLMISC::CRGBA color(255, 255, 255); + if(row != -1) + color = _Vegetable->Color.Gradients[row]; + + // update view and vegetable + QListWidgetItem *item = new QListWidgetItem(); + + item->setIcon(getRectColorIcon(QColor(color.R, color.G, color.B))); + + _ui.listWidget->addItem(item); + + _Vegetable->Color.Gradients.push_back(color); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableApperancePage::insNewColor() +{ + sint row = _ui.listWidget->currentRow(); + + // copy the current selected color + NLMISC::CRGBA color(255, 255, 255); + + if(row != -1) + color = _Vegetable->Color.Gradients[row]; + else + row++; + + // update view and vegetable + QListWidgetItem *item = new QListWidgetItem(); + + item->setIcon(getRectColorIcon(QColor(color.R, color.G, color.B))); + + _ui.listWidget->insertItem(row, item); + + _Vegetable->Color.Gradients.insert(_Vegetable->Color.Gradients.begin() + row ,color); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableApperancePage::removeColor() +{ + sint row = _ui.listWidget->currentRow(); + if (row == -1) return; + + // remove curSel from the list + QListWidgetItem *item = _ui.listWidget->takeItem(row); + delete item; + _Vegetable->Color.Gradients.erase(_Vegetable->Color.Gradients.begin() + row); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableApperancePage::getFromListColors() +{ + std::vector listVegetables; + Modules::veget().getListVegetables(listVegetables); + if (listVegetables.empty()) + return; + + QStringList items; + for(size_t i = 0; i < listVegetables.size(); ++i) + items << QString(listVegetables[i].c_str()); + + bool ok; + QString item = QInputDialog::getItem(this, tr("Select on other vegetables"), + tr("Select the other vegetable to copy color."), items, 0, false, &ok); + if (ok) + { + int i = items.indexOf(item); + + NL3D::CVegetable *otherVegetable = Modules::veget().getVegetable(i)->Vegetable; + + _Vegetable->Color.Gradients = otherVegetable->Color.Gradients; + + updateColorList(); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); + } +} + +void CVegetableApperancePage::updateColorList() +{ + // clear all + _ui.listWidget->clear(); + // fill list. + for(uint i = 0; i < _Vegetable->Color.Gradients.size(); i++) + { + NLMISC::CRGBA color = _Vegetable->Color.Gradients[i]; + QListWidgetItem *item = new QListWidgetItem(); + item->setIcon(getRectColorIcon(QColor(color.R, color.G, color.B))); + _ui.listWidget->addItem(item); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.h new file mode 100644 index 000000000..e68e1e904 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_appearance_page.h @@ -0,0 +1,71 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_APPEARANCE_PAGE_H +#define VEGETABLE_APPEARANCE_PAGE_H + +#include "ui_vegetable_apperance_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NL3D +{ + class CVegetable; +} + +namespace NLQT { + +class CVegetableApperancePage: public QWidget +{ + Q_OBJECT + +public: + CVegetableApperancePage(QWidget *parent = 0); + ~CVegetableApperancePage(); + + /// set the vegetble to edit. NULL will disable all the controls. + void setVegetableToEdit(NL3D::CVegetable *vegetable); + +private Q_SLOTS: + void setNoiseValueBendPhase(NLMISC::CNoiseValue value); + void setNoiseValueBendFactor(NLMISC::CNoiseValue value); + void setNoiseValueColor(NLMISC::CNoiseValue value); + void browseColor(QListWidgetItem * item); + void addNewColor(); + void insNewColor(); + void removeColor(); + void getFromListColors(); + +private: + void updateColorList(); + + // The vegetable to edit. + NL3D::CVegetable *_Vegetable; + + Ui::CVegetableApperancePage _ui; + +}; /* class CVegetableApperancePage */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_APPEARANCE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_apperance_form.ui b/code/nel/tools/3d/object_viewer_qt/src/vegetable_apperance_form.ui new file mode 100644 index 000000000..a1874f0a4 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_apperance_form.ui @@ -0,0 +1,166 @@ + + + CVegetableApperancePage + + + + 0 + 0 + 340 + 309 + + + + Form + + + + + + QTabWidget::South + + + QTabWidget::Rounded + + + 0 + + + true + + + false + + + + Bend Phase + + + + + + + + + + + + + + Bend Factor + + + + + + + + + + + + + + + + + Color Noise + + + + + + + Color setup + + + + + + + 0 + 0 + + + + Double cliclk to change color + + + QListView::LeftToRight + + + QListView::IconMode + + + + + + + + + + + :/images/list-remove.png:/images/list-remove.png + + + + + + + Get List from other + + + + + + + + + + + :/images/insert-horizontal.png:/images/insert-horizontal.png + + + + + + + + + + + :/images/list-add.png:/images/list-add.png + + + + + + + + + + Qt::Vertical + + + + 20 + 34 + + + + + + + + + NLQT::CVegetNoiseValueWidget + QGroupBox +
vegetable_noise_value_widget.h
+ 1 +
+
+ + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_form.ui b/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_form.ui new file mode 100644 index 000000000..563460e29 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_form.ui @@ -0,0 +1,382 @@ + + + CVegetableDensityPage + + + + 0 + 0 + 314 + 568 + + + + Form + + + + + + Vegetable mesh selection + + + + + + true + + + + + + + Browse + + + + + + + + + + + + Distance of creation + + + distanceSpinBox + + + + + + + 0 m + + + 1 + + + 5 + + + + + + + + + Density + + + + + + + MaxDensity + + + + + + MaxDensity enabled + + + + + + + false + + + + + + + + + + Angle setup + + + + + + Angle Mode: + + + + + + + Floor + + + + + + + Wall + + + + + + + Ceiling + + + + + + + AngleMin + + + angleMinHorizontalSlider + + + + + + + false + + + -1000 + + + 1000 + + + Qt::Horizontal + + + + + + + false + + + -99.000000000000000 + + + + + + + AngleMax + + + angleMaxHorizontalSlider + + + + + + + false + + + -1000 + + + 1000 + + + Qt::Horizontal + + + + + + + false + + + -99.000000000000000 + + + + + + + + + + Qt::Vertical + + + + 20 + 239 + + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CVegetNoiseValueWidget + QGroupBox +
vegetable_noise_value_widget.h
+ 1 +
+
+ + + + maxDensityCheckBox + toggled(bool) + maxDensityWidget + setEnabled(bool) + + + 99 + 221 + + + 110 + 244 + + + + + wallRadioButton + toggled(bool) + angleMinHorizontalSlider + setEnabled(bool) + + + 272 + 331 + + + 270 + 353 + + + + + wallRadioButton + toggled(bool) + angleMinDoubleSpinBox + setEnabled(bool) + + + 294 + 329 + + + 361 + 355 + + + + + wallRadioButton + toggled(bool) + angleMaxHorizontalSlider + setEnabled(bool) + + + 307 + 331 + + + 301 + 377 + + + + + wallRadioButton + toggled(bool) + angleMaxDoubleSpinBox + setEnabled(bool) + + + 312 + 333 + + + 355 + 378 + + + + + floorRadioButton + toggled(bool) + angleMinHorizontalSlider + setEnabled(bool) + + + 178 + 335 + + + 174 + 346 + + + + + ceilingRadioButton + toggled(bool) + angleMaxHorizontalSlider + setEnabled(bool) + + + 343 + 331 + + + 304 + 390 + + + + + floorRadioButton + toggled(bool) + angleMinDoubleSpinBox + setEnabled(bool) + + + 219 + 335 + + + 381 + 358 + + + + + ceilingRadioButton + toggled(bool) + angleMaxDoubleSpinBox + setEnabled(bool) + + + 385 + 329 + + + 398 + 377 + + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.cpp new file mode 100644 index 000000000..c49ba9111 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.cpp @@ -0,0 +1,304 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_density_page.h" + +// Qt includes +#include +#include + +// NeL includes +#include +#include +#include + +// Projects include +#include "modules.h" +#include "vegetable_dialog.h" + +namespace NLQT { + +CVegetableDensityPage::CVegetableDensityPage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + // Init Density widget. + _ui.densityGroupBox->setDefaultRangeAbs(NL_VEGETABLE_DENSITY_ABS_RANGE_MIN, NL_VEGETABLE_DENSITY_ABS_RANGE_MAX); + _ui.densityGroupBox->setDefaultRangeRand(NL_VEGETABLE_DENSITY_RAND_RANGE_MIN, NL_VEGETABLE_DENSITY_RAND_RANGE_MAX); + _ui.densityGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + // Init MaxDensity widget. + _ui.maxDensityWidget->setRange(0, NL_VEGETABLE_EDIT_DEFAULT_MAX_DENSITY); + _ui.maxDensityWidget->enableLowerBound(0, false); + + + connect(_ui.browseShapePushButton, SIGNAL(clicked()), this, SLOT(browseShapeVeget())); + connect(_ui.distanceSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setDistanceOfCreat(int))); + connect(_ui.densityGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setDensity(NLMISC::CNoiseValue))); + connect(_ui.maxDensityCheckBox, SIGNAL(toggled(bool)), this, SLOT(setEnabledMaxDensity(bool))); + connect(_ui.maxDensityWidget, SIGNAL(valueChanged(float)), this, SLOT(setMaxDensity(float))); + connect(_ui.floorRadioButton, SIGNAL(clicked()), this, SLOT(updateAngleMode())); + connect(_ui.wallRadioButton, SIGNAL(clicked()), this, SLOT(updateAngleMode())); + connect(_ui.ceilingRadioButton, SIGNAL(clicked()), this, SLOT(updateAngleMode())); + connect(_ui.angleMinHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setAngleMinSlider(int))); + connect(_ui.angleMaxHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setAngleMaxSlider(int))); + + setEnabled(false); +} + +CVegetableDensityPage::~CVegetableDensityPage() +{ +} + +void CVegetableDensityPage::setVegetableToEdit(NL3D::CVegetable *vegetable) +{ + _Vegetable = vegetable; + + if(_Vegetable) + { + setEnabled(true); + + // ShapeName + _ui.meshLineEdit->setText(QString(_Vegetable->ShapeName.c_str())); + + // Creation Distance. + // normalize creation distance for this editor. + _Vegetable->DistType = std::min(uint(_Vegetable->DistType), uint(_ui.distanceSpinBox->maximum()) ); + + // set Creation Distance. + _ui.distanceSpinBox->setValue(_Vegetable->DistType + 1); + + // set density widget + _ui.densityGroupBox->setNoiseValue(_Vegetable->Density, false); + + // init MaxDensity + if(_Vegetable->MaxDensity == -1) + { + // Disable the checkBox and the slider. + _ui.maxDensityCheckBox->setChecked(false); + + _PrecMaxDensityValue = NL_VEGETABLE_EDIT_DEFAULT_MAX_DENSITY; + } + else + { + // Enable the checkBox and the slider + _PrecMaxDensityValue = _Vegetable->MaxDensity; + _ui.maxDensityCheckBox->setChecked(true); + _ui.maxDensityWidget->setValue(_Vegetable->MaxDensity, false); + } + + // init AngleSetup. + // ---------- + NL3D::CVegetable::TAngleType angType = _Vegetable->getAngleType(); + + // enable only the one of interest. + _ui.floorRadioButton->blockSignals(true); + _ui.wallRadioButton->blockSignals(true); + _ui.ceilingRadioButton->blockSignals(true); + switch(angType) + { + case NL3D::CVegetable::AngleGround: + updateAngleMin(); + _ui.floorRadioButton->setChecked(true); + break; + case NL3D::CVegetable::AngleWall: + updateAngleMin(); + updateAngleMax(); + _ui.wallRadioButton->setChecked(true); + break; + case NL3D::CVegetable::AngleCeiling: + updateAngleMax(); + _ui.ceilingRadioButton->setChecked(true); + break; + } + _ui.floorRadioButton->blockSignals(false); + _ui.wallRadioButton->blockSignals(false); + _ui.ceilingRadioButton->blockSignals(false); + } + else + setEnabled(false); +} + +void CVegetableDensityPage::browseShapeVeget() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Open Vegetable Shape"), + ".", + tr("veget files (*.veget);;")); + if (!fileName.isEmpty()) + { + // Add search path for the .veget + NLMISC::CPath::addSearchPath (NLMISC::CFile::getPath(fileName.toStdString())); + + try + { + // update shapeName and view + _Vegetable->ShapeName = NLMISC::CFile::getFilename(fileName.toStdString()); + _ui.meshLineEdit->setText(QString(NLMISC::CFile::getFilename(fileName.toStdString()).c_str())); + + // update the name in the list-box + Q_EMIT vegetNameChanged(); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); + } + catch (NLMISC::EPathNotFound &ep) + { + QMessageBox::critical(this, "Can't open file", QString(ep.what()), QMessageBox::Ok); + } + } +} + +void CVegetableDensityPage::setDistanceOfCreat(int value) +{ + // Get the DistType, and just copy to vegetable. + _Vegetable->DistType = value - 1; + + // Since used to display name in selection listBox, must update the name + Q_EMIT vegetNameChanged(); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDensityPage::setEnabledMaxDensity(bool state) +{ + if(state) + { + // check, restore maxDensity + _Vegetable->MaxDensity = _PrecMaxDensityValue; + // enable dlg. + _ui.maxDensityWidget->setValue(_PrecMaxDensityValue); + _ui.maxDensityWidget->setEnabled(true); + } + else + { + // uncheck, bkup maxDenstiy + _PrecMaxDensityValue = _Vegetable->MaxDensity; + + _ui.maxDensityWidget->setEnabled(false); + + // and setup vegetable (disable MaxDensity). + _Vegetable->MaxDensity= -1; + } + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDensityPage::updateAngleMode() +{ + if (_ui.floorRadioButton->isChecked()) + { + _Vegetable->setAngleGround(0); + updateAngleMin(); + } + if (_ui.wallRadioButton->isChecked()) + { + _Vegetable->setAngleWall(-1, 1); + updateAngleMin(); + updateAngleMax(); + } + if (_ui.ceilingRadioButton->isChecked()) + { + _Vegetable->setAngleCeiling(0); + updateAngleMax(); + } + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDensityPage::setDensity(NLMISC::CNoiseValue value) +{ + _Vegetable->Density = value; + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDensityPage::setMaxDensity(float value) +{ + _Vegetable->MaxDensity = value; + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDensityPage::setAngleMinSlider(int pos) +{ + float angle = 180 * float(pos) / float(abs(_ui.angleMinHorizontalSlider->maximum() - _ui.angleMinHorizontalSlider->minimum())); + NLMISC::clamp(angle, -90, 90); + // make a sinus, because 90 => 1, and -90 =>-1 + float cosAngleMin = float(sin(angle * NLMISC::Pi / 180.f)); + + // setup vegetable. + if(_Vegetable->getAngleType()== NL3D::CVegetable::AngleWall) + _Vegetable->setAngleWall(cosAngleMin, _Vegetable->getCosAngleMax()); + else + _Vegetable->setAngleGround(cosAngleMin); + + // update view + _ui.angleMinDoubleSpinBox->setValue(angle); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDensityPage::setAngleMaxSlider(int pos) +{ + float angle = 180 * float(pos) / float(abs(_ui.angleMaxHorizontalSlider->maximum() - _ui.angleMaxHorizontalSlider->minimum())); + NLMISC::clamp(angle, -90, 90); + // make a sinus, because 90 => 1, and -90 =>-1 + float cosAngleMax = float(sin(angle * NLMISC::Pi / 180.f)); + + // setup vegetable. + if(_Vegetable->getAngleType() == NL3D::CVegetable::AngleWall) + _Vegetable->setAngleWall(_Vegetable->getCosAngleMin(), cosAngleMax); + else + _Vegetable->setAngleCeiling(cosAngleMax); + + // update view + _ui.angleMaxDoubleSpinBox->setValue(angle); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDensityPage::updateAngleMin() +{ + double angle = _Vegetable->getCosAngleMin(); + NLMISC::clamp(angle, -1, 1); + angle = asin(angle) * (180.f / NLMISC::Pi); + sint pos = sint(angle * abs(_ui.angleMinHorizontalSlider->maximum() - _ui.angleMinHorizontalSlider->minimum()) / 180); + NLMISC::clamp(pos, _ui.angleMinHorizontalSlider->minimum(), _ui.angleMinHorizontalSlider->maximum()); + _ui.angleMinHorizontalSlider->setSliderPosition(pos); +} + +void CVegetableDensityPage::updateAngleMax() +{ + double angle = _Vegetable->getCosAngleMax(); + NLMISC::clamp(angle, -1, 1); + angle = asin(angle) * (180.f / NLMISC::Pi); + sint pos = sint(angle * abs(_ui.angleMaxHorizontalSlider->maximum() - _ui.angleMaxHorizontalSlider->minimum()) / 180); + NLMISC::clamp(pos, _ui.angleMaxHorizontalSlider->minimum(), _ui.angleMaxHorizontalSlider->maximum()); + _ui.angleMaxHorizontalSlider->setSliderPosition(pos); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.h new file mode 100644 index 000000000..6700941fd --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_density_page.h @@ -0,0 +1,77 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_DENSITY_PAGE_H +#define VEGETABLE_DENSITY_PAGE_H + +#include "ui_vegetable_density_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NL3D +{ + class CVegetable; +} + +namespace NLQT { + +class CVegetableDensityPage: public QWidget +{ + Q_OBJECT + +public: + CVegetableDensityPage(QWidget *parent = 0); + ~CVegetableDensityPage(); + + /// set the vegetble to edit. NULL will disable all the controls. + void setVegetableToEdit(NL3D::CVegetable *vegetable); + +Q_SIGNALS: + void vegetNameChanged(); + +private Q_SLOTS: + void browseShapeVeget(); + void setDistanceOfCreat(int value); + void setEnabledMaxDensity(bool state); + void updateAngleMode(); + void setDensity(NLMISC::CNoiseValue value); + void setMaxDensity(float value); + void setAngleMinSlider(int pos); + void setAngleMaxSlider(int pos); + +private: + void updateAngleMin(); + void updateAngleMax(); + + // The vegetable to edit. + NL3D::CVegetable *_Vegetable; + + float _PrecMaxDensityValue; + + Ui::CVegetableDensityPage _ui; + +}; /* class CVegetableDensityPage */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_DENSITY_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.cpp new file mode 100644 index 000000000..579a3aadb --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.cpp @@ -0,0 +1,322 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_dialog.h" + +// Qt includes +#include +#include + +// NeL includes +#include +#include +#include "nel/misc/file.h" + +// Project includes +#include "modules.h" + +namespace NLQT { + +CVegetableDialog::CVegetableDialog(QWidget *parent) + : QDockWidget(parent) +{ + _ui.setupUi(this); + + connect(_ui.addtPushButton, SIGNAL(clicked()), this, SLOT(addVegetList())); + connect(_ui.insPushButton, SIGNAL(clicked()), this, SLOT(insVegetList())); + connect(_ui.delPushButton, SIGNAL(clicked()), this, SLOT(removeVegetList())); + connect(_ui.clearPushButton, SIGNAL(clicked()), this, SLOT(clearVegetList())); + connect(_ui.copyVegetPushButton, SIGNAL(clicked()), this, SLOT(copyVegetable())); + connect(_ui.loadVegetdescPushButton, SIGNAL(clicked()), this, SLOT(loadVegetdesc())); + connect(_ui.saveVegetdescPushButton, SIGNAL(clicked()), this, SLOT(saveVegetdesc())); + connect(_ui.loadVegetsetPushButton, SIGNAL(clicked()), this, SLOT(loadVegetset())); + connect(_ui.appendVegetsetPushButton, SIGNAL(clicked()), this, SLOT(appendVegetset())); + connect(_ui.saveVegetsetPushButton, SIGNAL(clicked()), this, SLOT(saveVegetset())); + connect(_ui.showVegetCheckBox, SIGNAL(toggled(bool)), this, SLOT(setVisibleVegetables(bool))); + connect(_ui.listWidget, SIGNAL(currentRowChanged(int)), this, SLOT(setCurrentItem(int))); +} + +CVegetableDialog::~CVegetableDialog() +{ +} + +void CVegetableDialog::loadVegetset() +{ + // ask name of the load new vegetset file + QString fileName = QFileDialog::getOpenFileName(this, tr("Load a new vegetset file"), + ".", + tr("vegetset files (*.vegetset);;")); + if (!fileName.isEmpty()) + { + NL3D::CTileVegetableDesc vegetSet; + // if succes to load the vegetSet + if(Modules::veget().loadVegetableSet(vegetSet, fileName.toStdString())) + { + // Delete all vegetables. + Modules::veget().clearVegetables(); + + // build them from list. + Modules::veget().appendVegetableSet(vegetSet); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); + + updateVegetList(); + } + } +} + +void CVegetableDialog::appendVegetset() +{ + // ask name of the load new vegetset file + QString fileName = QFileDialog::getOpenFileName(this, tr("Append vegetset file"), + ".", + tr("vegetset files (*.vegetset);;")); + if (!fileName.isEmpty()) + { + NL3D::CTileVegetableDesc vegetSet; + // if succes to load the vegetSet + if(Modules::veget().loadVegetableSet(vegetSet, fileName.toStdString())) + { + // Do not Delete any vegetables. + // build them from list. + Modules::veget().appendVegetableSet(vegetSet); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); + + updateVegetList(); + } + } +} + +void CVegetableDialog::saveVegetset() +{ + NL3D::CTileVegetableDesc vegetSet; + + // first build the vegetSet. + Modules::veget().buildVegetableSet(vegetSet); + + // Then try to save it. + QString fileName = QFileDialog::getSaveFileName(this, tr("Save Vegetable Set"), + ".", + tr("VegetSetFiles (*.vegetset);;")); + // after check + if (!fileName.isEmpty()) + { + NLMISC::COFile f; + + if( f.open(fileName.toStdString()) ) + { + try + { + // save the vegetable set + f.serial(vegetSet); + } + catch(NLMISC::EStream &e) + { + QMessageBox::critical(this, "Failed to save file!", QString(e.what()), QMessageBox::Ok); + } + } + else + QMessageBox::critical(this, "Failed to save file!", QString("Failed to open file for write!"), QMessageBox::Ok); + } + +} + +void CVegetableDialog::addVegetList() +{ + // Add a new vegetable to the list. + uint id = _ui.listWidget->count(); + + Modules::veget().insEmptyVegetDesc(id); + + // update view + QListWidgetItem *item = new QListWidgetItem(_ui.listWidget); + item->setText(QString(Modules::veget().getVegetable(id)->VegetableName.c_str())); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDialog::removeVegetList() +{ + sint id = _ui.listWidget->currentRow(); + if(id == -1) return; + + Modules::veget().delVegetDesc(id); + + QListWidgetItem *item = _ui.listWidget->takeItem(id); + delete item; + + id--; + + _ui.listWidget->setCurrentRow(id); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableDialog::insVegetList() +{ + sint id = _ui.listWidget->currentRow(); + if(id != -1) + { + // Add a new vegetable to the list. + Modules::veget().insEmptyVegetDesc(id); + + // update view + QListWidgetItem *item = new QListWidgetItem(); + item->setText(QString(Modules::veget().getVegetable(id)->VegetableName.c_str())); + _ui.listWidget->insertItem(id, item); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); + } + else + { + // perform like an add. + addVegetList(); + } +} + +void CVegetableDialog::clearVegetList() +{ + if (_ui.listWidget->count() == 0) return; + int ok = QMessageBox::question(this, "Clear List", QString("Clear all the list?"), QMessageBox::Yes | QMessageBox::No); + if (ok == QMessageBox::Yes) + { + Modules::veget().clearVegetables(); + + _ui.listWidget->clear(); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); + } +} + +void CVegetableDialog::copyVegetable() +{ +} + +void CVegetableDialog::loadVegetdesc() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Open Vegetable Descriptor"), + ".", + tr("vegetdesc files (*.vegetdesc);;")); + if (!fileName.isEmpty()) + { + NLMISC::CIFile f; + + if( f.open(fileName.toStdString()) ) + { + NL3D::CVegetable veget; + try + { + // read the vegetable + f.serial(veget); + + // Add a new vegetable to the list. + uint id = Modules::veget().addVegetDesc(veget); + + // update view + QListWidgetItem *item = new QListWidgetItem(_ui.listWidget); + item->setText(QString(Modules::veget().getVegetable(id)->VegetableName.c_str())); + + // update 3D view + Modules::veget().refreshVegetableDisplay(); + } + catch(NLMISC::EStream &e) + { + QMessageBox::critical(this, "Failed to load file!", QString(e.what()), QMessageBox::Ok); + } + } + else + QMessageBox::critical(this, "Failed to open file!", QString("Failed to open file for read!"), QMessageBox::Ok); + } +} + +void CVegetableDialog::saveVegetdesc() +{ + sint id = _ui.listWidget->currentRow(); + if(id == -1) return; + + CVegetableNode *vegetNode = Modules::veget().getVegetable(id); + + QString oldFileName = QString(vegetNode->VegetableName.c_str()) + ".vegetdesc"; + + // Then try to save it. + QString fileName = QFileDialog::getSaveFileName(this, tr("Save Vegetable Descriptor"), + oldFileName, + tr("VegetDescFiles (*.vegetdesc);;")); + // after check + if (!fileName.isEmpty()) + { + NLMISC::COFile f; + + if( f.open(fileName.toStdString()) ) + { + try + { + // save the vegetable + f.serial(*vegetNode->Vegetable); + } + catch(NLMISC::EStream &e) + { + QMessageBox::critical(this, "Failed to save file!", QString(e.what()), QMessageBox::Ok); + } + } + else + QMessageBox::critical(this, "Failed to save file!", QString("Failed to open file for write!"), QMessageBox::Ok); + } +} + +void CVegetableDialog::setVisibleVegetables(bool state) +{ + // update view. + Modules::veget().enableLandscapeVegetable(state); +} + +void CVegetableDialog::setCurrentItem(int row) +{ + NL3D::CVegetable *veget = NULL; + if (row != -1) + veget = Modules::veget().getVegetable(row)->Vegetable; + _ui.densityPage->setVegetableToEdit(veget); + _ui.appearancePage->setVegetableToEdit(veget); + _ui.scalePage->setVegetableToEdit(veget); + _ui.rotatePage->setVegetableToEdit(veget); +} + +void CVegetableDialog::updateVegetList() +{ + std::vector listVegetables; + Modules::veget().getListVegetables(listVegetables); + + _ui.listWidget->clear(); + + for (size_t i = 0; i < listVegetables.size(); i++) + { + QListWidgetItem *item = new QListWidgetItem(_ui.listWidget); + item->setText(QString(listVegetables[i].c_str())); + } +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.h new file mode 100644 index 000000000..a46551e0c --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog.h @@ -0,0 +1,74 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_DIALOG_H +#define VEGETABLE_DIALOG_H + +#include "ui_vegetable_dialog_form.h" + +// STL includes + +// NeL includes + +// Project includes +#include "vegetable_node.h" + +namespace NL3D +{ + class CTileVegetableDesc; +} + +namespace NLQT { + +class CVegetableDialog: public QDockWidget +{ + Q_OBJECT + +public: + CVegetableDialog(QWidget *parent = 0); + ~CVegetableDialog(); + +private Q_SLOTS: + void loadVegetset(); + void appendVegetset(); + void saveVegetset(); + + void addVegetList(); + void removeVegetList(); + void insVegetList(); + void clearVegetList(); + + void copyVegetable(); + void loadVegetdesc(); + void saveVegetdesc(); + + void setVisibleVegetables(bool state); + + void setCurrentItem(int row); + +private: + void updateVegetList(); + + Ui::CVegetableDialog _ui; + +}; /* class CVegetableDialog */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog_form.ui b/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog_form.ui new file mode 100644 index 000000000..b16477ee7 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_dialog_form.ui @@ -0,0 +1,274 @@ + + + CVegetableDialog + + + + 0 + 0 + 618 + 679 + + + + + :/images/veget.png:/images/veget.png + + + Vegetable editor + + + + + + + true + + + + + 0 + 0 + 598 + 640 + + + + + + + QLayout::SetFixedSize + + + + + Vegetables: + + + + + + + + 0 + 0 + + + + + 170 + 16777215 + + + + + + + + List edition + + + + + + Add vegetable + + + + + + + :/images/list-add.png:/images/list-add.png + + + + + + + Insert vegetable + + + + + + + :/images/insert-horizontal.png:/images/insert-horizontal.png + + + + + + + Remove vegetable + + + + + + + :/images/list-remove.png:/images/list-remove.png + + + + + + + Clear all vegetables + + + + + + + :/images/clear.png:/images/clear.png + + + + + + + false + + + Copy Vegetable + + + + + + + Load .vegetdesc + + + + + + + Save .vegetdesc + + + + + + + + + + List Save/Load + + + + + + Load .vegetset + + + + + + + Append .vegetset + + + + + + + Save .vegetset + + + + + + + + + + true + + + Display Vegetables + + + true + + + + + + + + + 0 + + + + Landscape + + + + + General/Density + + + + + Appearance + + + + + Scale + + + + + Rotation + + + + + + + + + + + + + + NLQT::CVegetableDensityPage + QWidget +
vegetable_density_page.h
+ 1 +
+ + NLQT::CVegetableLandscapePage + QWidget +
vegetable_landscape_page.h
+ 1 +
+ + NLQT::CVegetableScalePage + QWidget +
vegetable_scale_page.h
+ 1 +
+ + NLQT::CVegetableRotatePage + QWidget +
vegetable_rotate_page.h
+ 1 +
+ + NLQT::CVegetableApperancePage + QWidget +
vegetable_appearance_page.h
+ 1 +
+
+ + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.cpp new file mode 100644 index 000000000..0e4133d98 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.cpp @@ -0,0 +1,739 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_editor.h" + +// Qt includes +#include +#include + +// NeL includes +#include +#include +#include "nel/misc/file.h" +#include +#include +#include + +//#include + +// Project includes +#include "modules.h" +#include + +namespace NLQT { + +CVegetableEditor::CVegetableEditor(void) + : _VegetableLandscape(NULL), + _VegetableCollisionManager(NULL), + _VegetableCollisionEntity(NULL), + _Driver(NULL), _Scene(NULL) +{ + loadConfig(); +} + +CVegetableEditor::~CVegetableEditor(void) +{ + saveConfig(); +} + +void CVegetableEditor::init() +{ + //H_AUTO2 + nldebug("CVegetableEditor::init"); + + NL3D::CDriverUser *driver = dynamic_cast(Modules::objView().getDriver()); + _Driver = driver->getDriver(); + + NL3D::CSceneUser *scene = dynamic_cast(Modules::objView().getScene()); + _Scene = &scene->getScene(); +} + +void CVegetableEditor::release() +{ + //H_AUTO2 + nldebug("CVegetableEditor::release"); + + if(_VegetableCollisionEntity) + { + _VegetableCollisionManager->deleteEntity(_VegetableCollisionEntity); + _VegetableCollisionEntity= NULL; + } + if(_VegetableCollisionManager) + { + delete _VegetableCollisionManager; + _VegetableCollisionManager= NULL; + } + + // delete Landscape + if(_VegetableLandscape) + { + Modules::veget().getScene()->deleteModel(_VegetableLandscape); + _VegetableLandscape= NULL; + } +} + +bool CVegetableEditor::createVegetableLandscape() +{ + /// TODO: switch from C to U classes, if possible + // If not already done. + if(!_VegetableLandscape) + { + // load general landscape param + loadLandscapeSetup(); + + // create the landscape. + _VegetableLandscape= static_cast(Modules::veget().getScene()->createModel(NL3D::LandscapeModelId)); + + // create progress dialog + QProgressDialog progress("Loading TileBanks....", "Cancel", 0, 100); + progress.show(); + progress.setWindowModality(Qt::WindowModal); + progress.setValue(0); + try + { + if((_VegetableLandscapeTileBank.empty()) || (_VegetableLandscapeTileFarBank.empty()) || (_VegetableLandscapeZoneNames.empty())) + { + throw NLMISC::Exception("Landscape section object_viewer_qt.cfg not fully defined"); + } + + // Load The Bank files (copied from CLandscapeUser :) ). + // ================ + // load + NLMISC::CIFile bankFile(NLMISC::CPath::lookup(_VegetableLandscapeTileBank)); + _VegetableLandscape->Landscape.TileBank.serial(bankFile); + _VegetableLandscape->Landscape.TileBank.makeAllPathRelative(); + _VegetableLandscape->Landscape.TileBank.makeAllExtensionDDS(); + _VegetableLandscape->Landscape.TileBank.setAbsPath (""); + + // load + NLMISC::CIFile farbankFile(NLMISC::CPath::lookup(_VegetableLandscapeTileFarBank)); + _VegetableLandscape->Landscape.TileFarBank.serial(farbankFile); + if ( ! _VegetableLandscape->Landscape.initTileBanks() ) + { + nlwarning( "You need to recompute bank.farbank for the far textures" ); + } + bankFile.close(); + farbankFile.close(); + + // flushTiles. + // ================ + if(Modules::veget().getDriver()) + { + // progress + progress.setLabelText("Start loading Tiles..."); + // count nbText to load. + sint ts; + sint nbTextTotal = 0; + for (ts=0; ts < _VegetableLandscape->Landscape.TileBank.getTileSetCount (); ts++) + { + NL3D::CTileSet *tileSet =_VegetableLandscape->Landscape.TileBank.getTileSet (ts); + nbTextTotal += tileSet->getNumTile128(); + nbTextTotal += tileSet->getNumTile256(); + nbTextTotal += NL3D::CTileSet::count; + } + + // load. + sint nbTextDone= 0; + for (ts=0; ts < _VegetableLandscape->Landscape.TileBank.getTileSetCount (); ts++) + { + NL3D::CTileSet *tileSet=_VegetableLandscape->Landscape.TileBank.getTileSet (ts); + sint tl; + for (tl=0; tlgetNumTile128(); tl++, nbTextDone++) + { + _VegetableLandscape->Landscape.flushTiles (Modules::veget().getDriver(), (uint16)tileSet->getTile128(tl), 1); + progress.setValue(nbTextDone * 100 / nbTextTotal); + } + for (tl=0; tlgetNumTile256(); tl++, nbTextDone++) + { + _VegetableLandscape->Landscape.flushTiles (Modules::veget().getDriver(), (uint16)tileSet->getTile256(tl), 1); + progress.setValue(nbTextDone * 100 / nbTextTotal); + } + for (tl=0; tl < NL3D::CTileSet::count; tl++, nbTextDone++) + { + _VegetableLandscape->Landscape.flushTiles (Modules::veget().getDriver(), (uint16)tileSet->getTransition(tl)->getTile (), 1); + progress.setValue(nbTextDone * 100 / nbTextTotal); + } + } + progress.setValue(100); + } + + // misc setup. + // ================ + _VegetableLandscape->Landscape.setThreshold(_VegetableLandscapeThreshold); + _VegetableLandscape->Landscape.setTileNear(_VegetableLandscapeTileNear); + _VegetableLandscape->Landscape.setupStaticLight(_VegetableLandscapeDiffuse, _VegetableLandscapeAmbient, 1); + _VegetableLandscape->Landscape.loadVegetableTexture(_VegetableTexture); + _VegetableLandscape->Landscape.setupVegetableLighting(_VegetableAmbient, _VegetableDiffuse, _VegetableLightDir); + _VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin); + _VegetableLandscape->Landscape.setUpdateLightingFrequency(1); + + // Load the zones. + // ================ + // landscape recentering. + bool zoneLoaded = false; + NL3D::CAABBox landscapeBBox; + // progress + progress.setLabelText("Start loading Zones..."); + progress.setValue(0); + uint nbZones= (uint)_VegetableLandscapeZoneNames.size(); + for(uint i = 0; i < nbZones; i++) + { + // open the file + NLMISC::CIFile zoneFile(NLMISC::CPath::lookup(_VegetableLandscapeZoneNames[i])); + NL3D::CZone zone; + // load + zoneFile.serial(zone); + // append to landscape + _VegetableLandscape->Landscape.addZone(zone); + // progress + progress.setValue(i * 100 / nbZones); + // Add to the bbox. + if(!zoneLoaded) + { + zoneLoaded= true; + landscapeBBox.setCenter(zone.getZoneBB().getCenter()); + } + else + landscapeBBox.extend(zone.getZoneBB().getCenter()); + } + + // After All zone loaded, recenter the mouse listener on the landscape. + if(zoneLoaded) + { + NL3D::CMatrix matrix; + Modules::objView().get3dMouseListener()->setHotSpot(landscapeBBox.getCenter()); + matrix.setPos(landscapeBBox.getCenter()); + matrix.rotateX(-(float)NLMISC::Pi / 4); + matrix.translate(NLMISC::CVector(0, -1000, 0)); + Modules::objView().get3dMouseListener()->setMatrix(matrix); + } + + // Create collisions objects. + _VegetableCollisionManager= new NL3D::CVisualCollisionManager; + _VegetableCollisionManager->setLandscape(&_VegetableLandscape->Landscape); + _VegetableCollisionEntity= _VegetableCollisionManager->createEntity(); + progress.setValue(100); + } + catch (NLMISC::Exception &e) + { + // remove first possibly created collisions objects. + if(_VegetableCollisionEntity) + { + _VegetableCollisionManager->deleteEntity(_VegetableCollisionEntity); + _VegetableCollisionEntity= NULL; + } + + if(_VegetableCollisionManager) + { + delete _VegetableCollisionManager; + _VegetableCollisionManager= NULL; + } + + // remove the landscape + Modules::veget().getScene()->deleteModel(_VegetableLandscape); + + _VegetableLandscape= NULL; + + QMessageBox::critical(&Modules::mainWin(), "Failed to Load landscape", QString(e.what()), QMessageBox::Ok); + + return false; + } + } + + return true; +} + +void CVegetableEditor::showVegetableLandscape() +{ + if(_VegetableLandscape) + { + _VegetableLandscape->show(); + } +} + +void CVegetableEditor::hideVegetableLandscape() +{ + if(_VegetableLandscape) + { + _VegetableLandscape->hide(); + } +} + +void CVegetableEditor::enableLandscapeVegetable(bool enable) +{ + // update + _VegetableEnabled = enable; + + // update view. + if(_VegetableLandscape) + { + _VegetableLandscape->Landscape.enableVegetable(_VegetableEnabled); + } +} + +void CVegetableEditor::refreshVegetableLandscape(const NL3D::CTileVegetableDesc &tvdesc) +{ + // if landscape is displayed. + if(_VegetableLandscape) + { + // first disable the vegetable, to delete any vegetation + _VegetableLandscape->Landscape.enableVegetable(false); + + // Then change all the tileSet of all the TileBanks. + for (sint ts=0; ts<_VegetableLandscape->Landscape.TileBank.getTileSetCount (); ts++) + { + NL3D::CTileSet *tileSet=_VegetableLandscape->Landscape.TileBank.getTileSet (ts); + // change the vegetableTileDesc of this tileSet. + tileSet->setTileVegetableDesc(tvdesc); + } + + // re-Enable the vegetable (if wanted). + _VegetableLandscape->Landscape.enableVegetable(_VegetableEnabled); + } +} + +void CVegetableEditor::setVegetableWindPower(float w) +{ + _VegetableWindPower= w; + if(_VegetableLandscape) + _VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin); +} + +void CVegetableEditor::setVegetableWindBendStart(float w) +{ + _VegetableWindBendMin= w; + if(_VegetableLandscape) + _VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin); +} + +void CVegetableEditor::setVegetableWindFrequency(float w) +{ + _VegetableWindFreq= w; + if(_VegetableLandscape) + _VegetableLandscape->Landscape.setVegetableWind(_VegetableWindDir, _VegetableWindFreq, _VegetableWindPower, _VegetableWindBendMin); +} + +void CVegetableEditor::snapToGroundVegetableLandscape(bool enable) +{ + // update + if(_VegetableLandscape) + _VegetableSnapToGround = enable; +} + +void CVegetableEditor::setVegetableAmbientLight(const NLMISC::CRGBA &ambient) +{ + _VegetableLandscapeAmbient = ambient; + if(_VegetableLandscape) + _VegetableLandscape->Landscape.setupStaticLight(_VegetableLandscapeDiffuse, _VegetableLandscapeAmbient, 1); +} + +void CVegetableEditor::setVegetableDiffuseLight(const NLMISC::CRGBA &diffuse) +{ + _VegetableLandscapeDiffuse = diffuse; + if(_VegetableLandscape) + _VegetableLandscape->Landscape.setupStaticLight(_VegetableLandscapeDiffuse, _VegetableLandscapeAmbient, 1); +} + +void CVegetableEditor::update() +{ + // Vegetable: manage collision snapping if wanted and possible + if(_VegetableSnapToGround && _VegetableLandscape) + { + // get matrix from camera. + NLMISC::CMatrix matrix = Modules::objView().getScene()->getCam().getMatrix(); + + // snap To ground. + NLMISC::CVector pos = matrix.getPos(); + // if succes to snap to ground + if(_VegetableCollisionEntity->snapToGround(pos)) + { + pos.z+= _VegetableSnapHeight; + matrix.setPos(pos); + // reset the moveListener and the camera. + Modules::objView().get3dMouseListener()->setMatrix(matrix); + Modules::objView().getScene()->getCam().setMatrix(matrix); + } + } +} + +void CVegetableEditor::getListVegetables(std::vector &listVeget) +{ + listVeget.clear(); + for (size_t i = 0; i < _Vegetables.size(); i++) + listVeget.push_back(_Vegetables[i].VegetableName); +} + +bool CVegetableEditor::loadVegetableSet(NL3D::CTileVegetableDesc &vegetSet, std::string fileName) +{ + bool ok = true; + NLMISC::CIFile f; + + if( f.open(fileName) ) + { + try + { + // read the vegetable + f.serial(vegetSet); + } + catch(NLMISC::EStream &) + { + ok = false; + QMessageBox::critical(&Modules::mainWin(), "NeL Vegetable editor", QString("Failed to load file!"), QMessageBox::Ok); + } + } + else + { + ok = false; + QMessageBox::critical(&Modules::mainWin(), "NeL Vegetable editor", QString("Failed to load file!"), QMessageBox::Ok); + } + + return ok; +} + +void CVegetableEditor::buildVegetableSet(NL3D::CTileVegetableDesc &vegetSet, bool keepDefaultShapeName, bool keepHiden ) +{ + vegetSet.clear(); + float degToRad= (float)(NLMISC::Pi / 180.f); + + // build the list. + std::vector vegetables; + for(uint i = 0; i < _Vegetables.size(); i++) + { + // if don't want to keep ShapeNames, skip them. + if(!keepDefaultShapeName && _Vegetables[i].Vegetable->ShapeName == "") + continue; + // if don't want to keep hiden vegetables, skip them. + if(!keepHiden && !_Vegetables[i].Visible) + continue; + + vegetables.push_back(*_Vegetables[i].Vegetable); + // get dst index. + uint dstId= (uint)vegetables.size()-1; + // transform degrees in radians. + vegetables[dstId].Rx.Abs *= degToRad; + vegetables[dstId].Rx.Rand *= degToRad; + vegetables[dstId].Ry.Abs *= degToRad; + vegetables[dstId].Ry.Rand *= degToRad; + vegetables[dstId].Rz.Abs *= degToRad; + vegetables[dstId].Rz.Rand *= degToRad; + } + + // build the set. + vegetSet.build(vegetables); +} + +void CVegetableEditor::appendVegetableSet(NL3D::CTileVegetableDesc &vegetSet) +{ + float radToDeg = (float)(180.f / NLMISC::Pi); + + // for all distances Types. + for(uint distType = 0; distType < NL3D_VEGETABLE_BLOCK_NUMDIST; distType++) + { + // retrieve list of vegetable + const std::vector &vegetList = vegetSet.getVegetableList(distType); + + // for all of them + for(uint i = 0; i < vegetList.size();i++) + { + // append the vegetable to the list. + NL3D::CVegetable veget = vegetList[i]; + + // transform radians into degrees. + veget.Rx.Abs *= radToDeg; + veget.Rx.Rand *= radToDeg; + veget.Ry.Abs *= radToDeg; + veget.Ry.Rand *= radToDeg; + veget.Rz.Abs *= radToDeg; + veget.Rz.Rand *= radToDeg; + + // Add a new vegetable to the list. + _Vegetables.push_back( CVegetableNode()); + uint id= (uint)_Vegetables.size()-1; + _Vegetables[id].initVegetable(veget); + } + } +} + +void CVegetableEditor::clearVegetables() +{ + // delete all vegetables. + for(uint i=0; i<_Vegetables.size(); i++) + { + _Vegetables[i].deleteVegetable(); + } + _Vegetables.clear(); +} + +CVegetableNode *CVegetableEditor::getVegetable(sint id) +{ + if(id == -1) + return NULL; + else + return &_Vegetables[id]; +} + +uint CVegetableEditor::addVegetDesc(const NL3D::CVegetable &vegetable) +{ + // Add a new vegetable to the list. + _Vegetables.push_back(CVegetableNode ()); + uint id = (uint)_Vegetables.size()-1; + _Vegetables[id].initVegetable(vegetable); + return id; +} + +void CVegetableEditor::insEmptyVegetDesc(uint row) +{ + _Vegetables.insert(_Vegetables.begin() + row, CVegetableNode()); + _Vegetables[row].initDefaultVegetable(); +} + +void CVegetableEditor::delVegetDesc(uint id) +{ + // erase the vegetable from the list. + _Vegetables[id].deleteVegetable(); + _Vegetables.erase(_Vegetables.begin()+id); +} + +void CVegetableEditor::refreshVegetableDisplay() +{ + NL3D::CTileVegetableDesc vegetSet; + + // first build the vegetSet, but don't keep shapeName. and skip Hiden vegetables too + buildVegetableSet(vegetSet, false, false); + + // then refresh window. + Modules::veget().refreshVegetableLandscape(vegetSet); +} + +void CVegetableEditor::loadConfig() +{ + // vegetable display is true by default. + _VegetableEnabled = true; + _VegetableSnapToGround = false; + + // Load Landscape params. + // -------------- + // threshold + try + { + _VegetableLandscapeThreshold = Modules::config().getConfigFile().getVar("VegetLandscapeThreshold").asFloat(); + // clamp to avoid divide/0. + _VegetableLandscapeThreshold = std::max(_VegetableLandscapeThreshold, 0.001f); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableLandscapeThreshold = 0.003f; + } + // tilenear + try + { + _VegetableLandscapeTileNear = Modules::config().getConfigFile().getVar("VegetLandscapeTileNear").asFloat(); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableLandscapeTileNear= 50; + } + // ambient + try + { + NLMISC::CConfigFile::CVar &color = Modules::config().getConfigFile().getVar("VegetLandscapeAmbient"); + _VegetableLandscapeAmbient.R = color.asInt(0); + _VegetableLandscapeAmbient.G = color.asInt(1); + _VegetableLandscapeAmbient.B = color.asInt(2); + _VegetableLandscapeAmbient.A = color.asInt(3); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableLandscapeAmbient.set(80, 80, 80, 255); + } + // diffuse + try + { + NLMISC::CConfigFile::CVar &color= Modules::config().getConfigFile().getVar("VegetLandscapeDiffuse"); + _VegetableLandscapeDiffuse.R = color.asInt(0); + _VegetableLandscapeDiffuse.G = color.asInt(1); + _VegetableLandscapeDiffuse.B = color.asInt(2); + _VegetableLandscapeDiffuse.A = color.asInt(3); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableLandscapeDiffuse.set(255, 255, 255, 255); + } + // Snapping + try + { + _VegetableSnapHeight = Modules::config().getConfigFile().getVar("VegetLandscapeSnapHeight").asFloat(); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableSnapHeight = 1.70f; + } + + + // Load Vegetable params. + // -------------- + + // vegetable ambient + try + { + NLMISC::CConfigFile::CVar &color = Modules::config().getConfigFile().getVar("VegetAmbient"); + _VegetableAmbient.R = color.asInt(0); + _VegetableAmbient.G = color.asInt(1); + _VegetableAmbient.B = color.asInt(2); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableAmbient.set(80, 80, 80, 255); + } + // vegetable diffuse + try + { + NLMISC::CConfigFile::CVar &color = Modules::config().getConfigFile().getVar("VegetDiffuse"); + // setup to behave correclty ie as maxLightFactor: + sint R = color.asInt(0) - _VegetableAmbient.R; NLMISC::clamp(R, 0, 255); _VegetableDiffuse.R = R; + sint G = color.asInt(1) - _VegetableAmbient.G; NLMISC::clamp(G, 0, 255); _VegetableDiffuse.G = G; + sint B = color.asInt(2) - _VegetableAmbient.B; NLMISC::clamp(B, 0, 255); _VegetableDiffuse.B = B; + } + catch (NLMISC::EUnknownVar &) + { + sint R = 255 - _VegetableAmbient.R; NLMISC::clamp(R, 0, 255); _VegetableDiffuse.R = R; + sint G = 255 - _VegetableAmbient.G; NLMISC::clamp(G, 0, 255); _VegetableDiffuse.G = G; + sint B = 255 - _VegetableAmbient.B; NLMISC::clamp(B, 0, 255); _VegetableDiffuse.B = B; + } + // vegetable lightDir + try + { + NLMISC::CConfigFile::CVar &var = Modules::config().getConfigFile().getVar("VegetLightDir"); + _VegetableLightDir.x = var.asFloat(0); + _VegetableLightDir.y = var.asFloat(1); + _VegetableLightDir.z = var.asFloat(2); + _VegetableLightDir.normalize(); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableLightDir.set(0, 1, -1); + _VegetableLightDir.normalize(); + } + + // windDir + try + { + NLMISC::CConfigFile::CVar &var = Modules::config().getConfigFile().getVar("VegetWindDir"); + _VegetableWindDir.x = var.asFloat(0); + _VegetableWindDir.y = var.asFloat(1); + _VegetableWindDir.z = var.asFloat(2); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableWindDir.x = 0.5f; + _VegetableWindDir.y = 0.5f; + _VegetableWindDir.z = 0; + } + // windFreq + try + { + _VegetableWindFreq = Modules::config().getConfigFile().getVar("VegetWindFreq").asFloat(); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableWindFreq= 0.5; + } + // windPower + try + { + _VegetableWindPower = Modules::config().getConfigFile().getVar("VegetWindPower").asFloat(); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableWindPower= 1; + } + // windBendMin + try + { + _VegetableWindBendMin = Modules::config().getConfigFile().getVar("VegetWindBendMin").asFloat(); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableWindBendMin= 0; + } +} + +void CVegetableEditor::loadLandscapeSetup() +{ + // Load landscape setup + // -------------- + try + { + // tileBank setup. + _VegetableLandscapeTileBank = Modules::config().getConfigFile().getVar("VegetTileBank").asString(); + _VegetableLandscapeTileFarBank = Modules::config().getConfigFile().getVar("VegetTileFarBank").asString(); + // zone list. + _VegetableLandscapeZoneNames.clear(); + NLMISC::CConfigFile::CVar &zones = Modules::config().getConfigFile().getVar("VegetLandscapeZones"); + for (uint i=0; i<(uint)zones.size(); i++) + _VegetableLandscapeZoneNames.push_back(zones.asString(i).c_str()); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableLandscapeTileBank.clear(); + _VegetableLandscapeTileFarBank.clear(); + _VegetableLandscapeZoneNames.clear(); + } + + // Load Vegetable params. + // -------------- + + // vegetable texture + try + { + _VegetableTexture = Modules::config().getConfigFile().getVar("VegetTexture").asString(); + } + catch (NLMISC::EUnknownVar &) + { + _VegetableTexture= ""; + } +} + +void CVegetableEditor::saveConfig() +{ + Modules::config().getConfigFile().getVar("VegetLandscapeThreshold").setAsFloat(_VegetableLandscapeThreshold); + Modules::config().getConfigFile().getVar("VegetLandscapeTileNear").setAsFloat(_VegetableLandscapeTileNear); + + Modules::config().getConfigFile().getVar("VegetLandscapeAmbient").setAsInt(_VegetableLandscapeAmbient.R, 0); + Modules::config().getConfigFile().getVar("VegetLandscapeAmbient").setAsInt(_VegetableLandscapeAmbient.G, 1); + Modules::config().getConfigFile().getVar("VegetLandscapeAmbient").setAsInt(_VegetableLandscapeAmbient.B, 2); + Modules::config().getConfigFile().getVar("VegetLandscapeAmbient").setAsInt(_VegetableLandscapeAmbient.A, 3); + + Modules::config().getConfigFile().getVar("VegetLandscapeDiffuse").setAsInt(_VegetableLandscapeDiffuse.R, 0); + Modules::config().getConfigFile().getVar("VegetLandscapeDiffuse").setAsInt(_VegetableLandscapeDiffuse.G, 1); + Modules::config().getConfigFile().getVar("VegetLandscapeDiffuse").setAsInt(_VegetableLandscapeDiffuse.B, 2); + Modules::config().getConfigFile().getVar("VegetLandscapeAmbient").setAsInt(_VegetableLandscapeAmbient.A, 3); + + Modules::config().getConfigFile().getVar("VegetWindDir").setAsFloat(_VegetableWindDir.x, 0); + Modules::config().getConfigFile().getVar("VegetWindDir").setAsFloat(_VegetableWindDir.y, 1); + Modules::config().getConfigFile().getVar("VegetWindDir").setAsFloat(_VegetableWindDir.z, 2); + + Modules::config().getConfigFile().getVar("VegetWindPower").setAsFloat(_VegetableWindPower); + Modules::config().getConfigFile().getVar("VegetWindFreq").setAsFloat(_VegetableWindFreq); + Modules::config().getConfigFile().getVar("VegetWindBendMin").setAsFloat(_VegetableWindBendMin); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.h new file mode 100644 index 000000000..a61238b6f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_editor.h @@ -0,0 +1,252 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_EDITOR_H +#define VEGETABLE_EDITOR_H + +// STL includes +#include +#include + +// NeL includes +#include +#include +#include + +// Project includes +#include "vegetable_node.h" + +// Defaults Sliders ranges. +#define NL_VEGETABLE_EDIT_DEFAULT_MAX_DENSITY 50.f + +// General Frequence +#define NL_VEGETABLE_FREQ_RANGE_MIN 0.0001f +#define NL_VEGETABLE_FREQ_RANGE_MAX 1.f +#define NL_VEGETABLE_FREQ_DEFAULT 0.1f + +// Density +#define NL_VEGETABLE_DENSITY_ABS_RANGE_MIN -10.f +#define NL_VEGETABLE_DENSITY_ABS_RANGE_MAX 10.f +#define NL_VEGETABLE_DENSITY_RAND_RANGE_MIN 0.f +#define NL_VEGETABLE_DENSITY_RAND_RANGE_MAX 10.f +#define NL_VEGETABLE_DENSITY_ABS_DEFAULT 0.f +#define NL_VEGETABLE_DENSITY_RAND_DEFAULT 0.25f + +// BendPhase +#define NL_VEGETABLE_BENDPHASE_RANGE_MIN 0.f +#define NL_VEGETABLE_BENDPHASE_RANGE_MAX 2.f +#define NL_VEGETABLE_BENDPHASE_ABS_DEFAULT 0.f +#define NL_VEGETABLE_BENDPHASE_RAND_DEFAULT 2.f + +// BendFactor +#define NL_VEGETABLE_BENDFACTOR_RANGE_MIN 0.f +#define NL_VEGETABLE_BENDFACTOR_RANGE_MAX 1.f +#define NL_VEGETABLE_BENDFACTOR_ABS_DEFAULT 0.5f +#define NL_VEGETABLE_BENDFACTOR_RAND_DEFAULT 0.5f + +// ColorNoise +#define NL_VEGETABLE_COLOR_RANGE_MIN -1.f +#define NL_VEGETABLE_COLOR_RANGE_MAX 3.f +#define NL_VEGETABLE_COLOR_ABS_DEFAULT -1.f +#define NL_VEGETABLE_COLOR_RAND_DEFAULT 3.f + +// Scale +#define NL_VEGETABLE_SCALE_RANGE_MIN 0.f +#define NL_VEGETABLE_SCALE_RANGE_MAX 1.f +#define NL_VEGETABLE_SCALE_ABS_DEFAULT 0.5f +#define NL_VEGETABLE_SCALE_RAND_DEFAULT 0.5f + +// Rotate +#define NL_VEGETABLE_ROTATE_RANGE_MIN -90.f +#define NL_VEGETABLE_ROTATE_RANGE_MAX 90.f +#define NL_VEGETABLE_ROTATEX_ABS_DEFAULT -20.f +#define NL_VEGETABLE_ROTATEX_RAND_DEFAULT 40.f +#define NL_VEGETABLE_ROTATEY_ABS_DEFAULT 0.f +#define NL_VEGETABLE_ROTATEY_RAND_DEFAULT 0.f +#define NL_VEGETABLE_ROTATEZ_ABS_DEFAULT 0.f +#define NL_VEGETABLE_ROTATEZ_RAND_DEFAULT 3000.f +#define NL_VEGETABLE_ROTATEZ_FREQ_DEFAULT 10.f + +// BendFreq +#define NL_VEGETABLE_BENDFREQ_RANGE_MIN 0.f +#define NL_VEGETABLE_BENDFREQ_RANGE_MAX 4.f + +namespace NL3D +{ + class CTileVegetableDesc; + class CLandscapeModel; + class CVisualCollisionManager; + class CVisualCollisionEntity; + class CScene; + class IDriver; +} + + +namespace NLQT { +/** +@class CVegetableEditor +@brief Loading and viewing .zonel, .vegetset files. +Dynamic control of parameters of the landscape (wind/colors) and +list micro-vegetation node (load/save veget, create/add/ins/del list micro-vegetation node). +*/ +class CVegetableEditor +{ +public: + CVegetableEditor(void); + ~CVegetableEditor(void); + + void init(); + + void release(); + + /// @name Landscape control + //@{ + + /// @return true if landscape is created + bool isVegetableLandscapeCreated() const {return _VegetableLandscape != NULL;} + + /// Load the landscape with help of setup in object_viewer_qt.cfg. return true if OK. + bool createVegetableLandscape(); + + /// If created, show the landscape + void showVegetableLandscape(); + + /// If created, hide the landscape + void hideVegetableLandscape(); + + /// Display vegetable with landscape + void enableLandscapeVegetable(bool enable); + + /// Refresh the vegetables in landscape, with the new vegetableSet + void refreshVegetableLandscape(const NL3D::CTileVegetableDesc &tvdesc); + + /// Get vegetable Wind wetup. + float getVegetableWindPower() const {return _VegetableWindPower;} + float getVegetableWindBendStart() const {return _VegetableWindBendMin;} + float getVegetableWindFrequency() const {return _VegetableWindFreq;} + + /// Set vegetable Wind wetup (updat view if possible) + void setVegetableWindPower(float w); + void setVegetableWindBendStart(float w); + void setVegetableWindFrequency(float w); + + void setVegetableAmbientLight(const NLMISC::CRGBA &ambient); + void setVegetableDiffuseLight(const NLMISC::CRGBA &diffuse); + + NLMISC::CRGBA getVegetableAmbientLight() const {return _VegetableLandscapeAmbient;} + NLMISC::CRGBA getVegetableDiffuseLight() const {return _VegetableLandscapeDiffuse;} + + /// If enable, snap the camera to the ground of the landscape. + void snapToGroundVegetableLandscape(bool enable); + //@} + + /// @name Vegetable control + //@{ + + /// Refresh vegetable display even if box unchecked. + void refreshVegetableDisplay(); + + /// Load a vegetSet + bool loadVegetableSet(NL3D::CTileVegetableDesc &vegetSet, std::string fileName); + + /// Build the vegetSet from the current _Vegetables + /// NB: transform Rotate Angle in Radians. + /// @param keepDefaultShapeName - if true, then vegetables with a ShapeName=="" are kept. + /// @param keepHiden - if true, then vegetables maked as hiden in ObjectViewer are kept. + void buildVegetableSet(NL3D::CTileVegetableDesc &vegetSet, bool keepDefaultShapeName = true, bool keepHiden = true ); + + /// Append the vegetSet to the current _Vegetables + /// NB: transform Rotate Angle in Degrees. + void appendVegetableSet(NL3D::CTileVegetableDesc &vegetSet); + + /// Clear all vegetables. + void clearVegetables(); + + /// Get full list vegetables from the landscape + /// @param listVeget - ref of return list vegetables + void getListVegetables(std::vector &listVeget); + + /// Create and add veget node to list + uint addVegetDesc(const NL3D::CVegetable &vegetable); + + /// Create empty veget node and insert to list + void insEmptyVegetDesc(uint row); + + /// Remove veget node from the list + void delVegetDesc(uint id); + + CVegetableNode *getVegetable(sint id); + //@} + + /// Update snap the camera to the ground of the landscape (if enabled snapToGroundVegetableLandscape()). + void update(); + + NL3D::IDriver *getDriver() const { return _Driver; } + + NL3D::CScene *getScene() const { return _Scene; } + +private: + void loadConfig(); + + void loadLandscapeSetup(); + + void saveConfig(); + + NL3D::CLandscapeModel *_VegetableLandscape; + + // File info to build it + std::string _VegetableLandscapeTileBank; + std::string _VegetableLandscapeTileFarBank; + std::vector _VegetableLandscapeZoneNames; + + // Misc. + float _VegetableLandscapeThreshold; + float _VegetableLandscapeTileNear; + float _VegetableLandscapeMultiply; + NLMISC::CRGBA _VegetableLandscapeAmbient; + NLMISC::CRGBA _VegetableLandscapeDiffuse; + std::string _VegetableTexture; + NLMISC::CRGBA _VegetableAmbient; + NLMISC::CRGBA _VegetableDiffuse; + NLMISC::CVector _VegetableLightDir; + // Vegetable wind. + NLMISC::CVector _VegetableWindDir; + float _VegetableWindFreq; + float _VegetableWindPower; + float _VegetableWindBendMin; + + bool _VegetableEnabled; + + // Collision + bool _VegetableSnapToGround; + float _VegetableSnapHeight; + NL3D::CVisualCollisionManager *_VegetableCollisionManager; + NL3D::CVisualCollisionEntity *_VegetableCollisionEntity; + + // The vegetable List. + std::vector _Vegetables; + + NL3D::IDriver *_Driver; + NL3D::CScene *_Scene; + +}; /* class CVegetableEditor */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_EDITOR_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_form.ui b/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_form.ui new file mode 100644 index 000000000..352fe72f4 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_form.ui @@ -0,0 +1,268 @@ + + + CVegetableLandscapePage + + + + 0 + 0 + 374 + 475 + + + + Form + + + + + + false + + + Setup Wind + + + + + + + + Power: + + + powerHorizontalSlider + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 10.000000000000000 + + + + + + + + + 1000 + + + Qt::Horizontal + + + + + + + + + Bend Start: + + + bendStartHorizontalSlider + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 3 + + + 1.000000000000000 + + + + + + + + + 1000 + + + Qt::Horizontal + + + + + + + + + Frequency + + + frequencyHorizontalSlider + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + QAbstractSpinBox::NoButtons + + + 10.000000000000000 + + + + + + + + + 1000 + + + Qt::Horizontal + + + + + + + + + + false + + + QTabWidget::South + + + 0 + + + + Ambient color + + + + + + + + + + Diffuse color + + + + + + + + + + + + + Show Vegetable Lands + + + + + + + false + + + Snap Camera To Ground + + + + + + + Qt::Vertical + + + + 20 + 168 + + + + + + + + + NLQT::CColorEditWidget + QWidget +
color_edit_widget.h
+ 1 +
+
+ + powerHorizontalSlider + bendStartHorizontalSlider + frequencyHorizontalSlider + colorTabWidget + showLandscapeCheckBox + snapCameraToGroundCheckBox + powerDoubleSpinBox + bendStartDoubleSpinBox + frequiencyDoubleSpinBox + + + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.cpp new file mode 100644 index 000000000..aaef671bf --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.cpp @@ -0,0 +1,135 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_landscape_page.h" + +// Project includes +#include "modules.h" + +#define NL_VEGETABLE_EDIT_WIND_MAX_POWER 10.f +#define NL_VEGETABLE_EDIT_WIND_MAX_FREQUENCY 10.f +#define NL_VEGETABLE_EDIT_WIND_MAX_BENDSTART 1.f + +namespace NLQT { + +CVegetableLandscapePage::CVegetableLandscapePage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + connect(_ui.powerHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setPowerSlider(int))); + connect(_ui.bendStartHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setBendStartSlider(int))); + connect(_ui.frequencyHorizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(setFrequencySlider(int))); + connect(_ui.showLandscapeCheckBox, SIGNAL(toggled(bool)), this, SLOT(setVisibleLandscape(bool))); + connect(_ui.snapCameraToGroundCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSnapCameraToGround(bool))); + connect(_ui.diffuseColorWidget, SIGNAL(colorChanged(NLMISC::CRGBA)), this, SLOT(setColorDiffuse(NLMISC::CRGBA))); + connect(_ui.ambientColorwidget, SIGNAL(colorChanged(NLMISC::CRGBA)), this, SLOT(setColorAmbient(NLMISC::CRGBA))); + + float windPower = Modules::veget().getVegetableWindPower(); + NLMISC::clamp(windPower, 0, NL_VEGETABLE_EDIT_WIND_MAX_POWER); + _ui.powerHorizontalSlider->setValue(int(windPower * _ui.powerHorizontalSlider->maximum() / NL_VEGETABLE_EDIT_WIND_MAX_POWER)); + + float bendStar = Modules::veget().getVegetableWindBendStart(); + NLMISC::clamp(bendStar, 0, NL_VEGETABLE_EDIT_WIND_MAX_BENDSTART); + _ui.bendStartHorizontalSlider->setValue(int(bendStar * _ui.bendStartHorizontalSlider->maximum() / NL_VEGETABLE_EDIT_WIND_MAX_BENDSTART)); + + float frequency = Modules::veget().getVegetableWindFrequency(); + NLMISC::clamp(frequency, 0, NL_VEGETABLE_EDIT_WIND_MAX_FREQUENCY); + _ui.frequencyHorizontalSlider->setValue(int(frequency * _ui.frequencyHorizontalSlider->maximum() / NL_VEGETABLE_EDIT_WIND_MAX_FREQUENCY)); + + _ui.diffuseColorWidget->setColor(Modules::veget().getVegetableDiffuseLight(), false); + _ui.ambientColorwidget->setColor(Modules::veget().getVegetableAmbientLight(), false); +} + +CVegetableLandscapePage::~CVegetableLandscapePage() +{ +} + +void CVegetableLandscapePage::setPowerSlider(int value) +{ + float windPower = float(value * NL_VEGETABLE_EDIT_WIND_MAX_POWER / _ui.powerHorizontalSlider->maximum()); + _ui.powerDoubleSpinBox->setValue(windPower); + Modules::veget().setVegetableWindPower(windPower); +} + +void CVegetableLandscapePage::setBendStartSlider(int value) +{ + float bendStar = float(value * NL_VEGETABLE_EDIT_WIND_MAX_BENDSTART / _ui.bendStartHorizontalSlider->maximum()); + _ui.bendStartDoubleSpinBox->setValue(bendStar); + Modules::veget().setVegetableWindBendStart(bendStar); +} + +void CVegetableLandscapePage::setFrequencySlider(int value) +{ + float frequency = float(value * NL_VEGETABLE_EDIT_WIND_MAX_FREQUENCY / _ui.frequencyHorizontalSlider->maximum()); + _ui.frequiencyDoubleSpinBox->setValue(frequency); + Modules::veget().setVegetableWindFrequency(frequency); +} + +void CVegetableLandscapePage::setVisibleLandscape(bool state) +{ + if(state) + { + // Landscape not created ?? + if(!Modules::veget().isVegetableLandscapeCreated()) + { + // if success to create / Load the landscape. + if(Modules::veget().createVegetableLandscape()) + { + _ui.windGroupBox->setEnabled(true); + _ui.colorTabWidget->setEnabled(true); + _ui.snapCameraToGroundCheckBox->setEnabled(true); + // refresh view, independently of checkBox + //doRefreshVegetableDisplay(); + } + else + { + // Failed in load, never retry. + _ui.showLandscapeCheckBox->setChecked(false); + } + } + + // show the landscape + Modules::veget().showVegetableLandscape(); + Q_EMIT changeVisibleLandscape(true); + } + else + { + Modules::veget().hideVegetableLandscape(); + Q_EMIT changeVisibleLandscape(false); + } +} + +void CVegetableLandscapePage::setSnapCameraToGround(bool state) +{ + Modules::veget().snapToGroundVegetableLandscape(state); +} + +void CVegetableLandscapePage::setColorAmbient(NLMISC::CRGBA color) +{ + Modules::veget().setVegetableAmbientLight(color); +} + +void CVegetableLandscapePage::setColorDiffuse(NLMISC::CRGBA color) +{ + Modules::veget().setVegetableDiffuseLight(color); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.h new file mode 100644 index 000000000..b545bc52f --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_landscape_page.h @@ -0,0 +1,61 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_LANDSCAPE_PAGE_H +#define VEGETABLE_LANDSCAPE_PAGE_H + +#include "ui_vegetable_landscape_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NLQT { + +class CVegetableLandscapePage: public QWidget +{ + Q_OBJECT + +public: + CVegetableLandscapePage(QWidget *parent = 0); + ~CVegetableLandscapePage(); + +Q_SIGNALS: + void changeVisibleLandscape(bool state); + +private Q_SLOTS: + void setPowerSlider(int value); + void setBendStartSlider(int value); + void setFrequencySlider(int value); + void setVisibleLandscape(bool state); + void setSnapCameraToGround(bool state); + void setColorAmbient(NLMISC::CRGBA color); + void setColorDiffuse(NLMISC::CRGBA color); + +private: + + Ui::CVegetableLandscapePage _ui; + +}; /* class CVegetableLandscapePage */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_LANDSCAPE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_node.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_node.cpp new file mode 100644 index 000000000..5403bd8f4 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_node.cpp @@ -0,0 +1,130 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_node.h" + +#include "modules.h" + +namespace NLQT { + +static const char *NL_DefaultVegetName= ""; + +CVegetableNode::CVegetableNode(void) +{ + Vegetable = NULL; + VegetableName = NL_DefaultVegetName; + Visible = true; +} + +void CVegetableNode::initDefaultVegetable() +{ + Vegetable= new NL3D::CVegetable; + // update vegetableName according to Vegetable + updateVegetableName(); + + // init Vegetable with some good default values. + + // General/Density. + // Density. + Vegetable->Density.Abs = NL_VEGETABLE_DENSITY_ABS_DEFAULT; + Vegetable->Density.Rand = NL_VEGETABLE_DENSITY_RAND_DEFAULT; + Vegetable->Density.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + // disable MaxDensity + Vegetable->MaxDensity = -1; + // Leave ShapeName to "" + // Default DistType is always 0. + Vegetable->DistType = 0; + + + // Apperance + // BendPhase + Vegetable->BendPhase.Abs = NL_VEGETABLE_BENDPHASE_ABS_DEFAULT; + Vegetable->BendPhase.Rand = NL_VEGETABLE_BENDPHASE_RAND_DEFAULT; + Vegetable->BendPhase.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + // BendFactor + Vegetable->BendFactor.Abs = NL_VEGETABLE_BENDFACTOR_ABS_DEFAULT; + Vegetable->BendFactor.Rand = NL_VEGETABLE_BENDFACTOR_RAND_DEFAULT; + Vegetable->BendFactor.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + // Color + Vegetable->Color.NoiseValue.Abs = NL_VEGETABLE_COLOR_ABS_DEFAULT; + Vegetable->Color.NoiseValue.Rand = NL_VEGETABLE_COLOR_RAND_DEFAULT; + Vegetable->Color.NoiseValue.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + + // Scale + // ScaleXY + Vegetable->Sxy.Abs = NL_VEGETABLE_SCALE_ABS_DEFAULT; + Vegetable->Sxy.Rand = NL_VEGETABLE_SCALE_RAND_DEFAULT; + Vegetable->Sxy.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + // ScaleZ. + Vegetable->Sz.Abs = NL_VEGETABLE_SCALE_ABS_DEFAULT; + Vegetable->Sz.Rand = NL_VEGETABLE_SCALE_RAND_DEFAULT; + Vegetable->Sz.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + + // Rotate + // RotateX + Vegetable->Rx.Abs = NL_VEGETABLE_ROTATEX_ABS_DEFAULT; + Vegetable->Rx.Rand = NL_VEGETABLE_ROTATEX_RAND_DEFAULT; + Vegetable->Rx.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + // RotateY + Vegetable->Ry.Abs = NL_VEGETABLE_ROTATEY_ABS_DEFAULT; + Vegetable->Ry.Rand = NL_VEGETABLE_ROTATEY_RAND_DEFAULT; + Vegetable->Ry.Frequency = NL_VEGETABLE_FREQ_DEFAULT; + // RotateZ + Vegetable->Rz.Abs = NL_VEGETABLE_ROTATEZ_ABS_DEFAULT; + Vegetable->Rz.Rand = NL_VEGETABLE_ROTATEZ_RAND_DEFAULT; + Vegetable->Rz.Frequency = NL_VEGETABLE_ROTATEZ_FREQ_DEFAULT; + +} + +void CVegetableNode::initVegetable(const NL3D::CVegetable &vegetable) +{ + Vegetable = new NL3D::CVegetable(vegetable); + // update vegetableName according to Vegetable + updateVegetableName(); +} + + +void CVegetableNode::updateVegetableName() +{ + // Build the vegetable Name according to the ShapeName + if(Vegetable->ShapeName == "") + { + VegetableName = NL_DefaultVegetName; + } + else + { + std::string::size_type pos = Vegetable->ShapeName.find(".veget"); + VegetableName= Vegetable->ShapeName.substr(0, pos); + // And (to be clearer) append distance of creation. + char str[256]; + sprintf(str, " - %dm", (Vegetable->DistType + 1) * 10); + VegetableName += str; + // NB: if you add info with other parameters, you must use updateCurSelVegetableName() if they change + } +} + +void CVegetableNode::deleteVegetable() +{ + delete Vegetable; + Vegetable = NULL; + VegetableName = NL_DefaultVegetName; +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_node.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_node.h new file mode 100644 index 000000000..aa739ac21 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_node.h @@ -0,0 +1,61 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_NODE_H +#define VEGETABLE_NODE_H + +// STL includes +#include + +// NeL includes +#include + +namespace NLQT { + +class CVegetableNode +{ +public: + /// Constructor + CVegetableNode(void); + + // init the vegetable + void initDefaultVegetable(); + + void initVegetable(const NL3D::CVegetable &vegetable); + + // update VegetableName according to Vegetable + void updateVegetableName(); + + // delete the vegetable + void deleteVegetable(); + + // The vegetable + NL3D::CVegetable *Vegetable; + + // The name of this vegetable. + std::string VegetableName; + + // Visibility. Editor feature only + bool Visible; + +}; /* class CVegetableNode */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_NODE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_form.ui b/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_form.ui new file mode 100644 index 000000000..a30bf0883 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_form.ui @@ -0,0 +1,168 @@ + + + CVegetNoiseValueWidget + + + + 0 + 0 + 312 + 110 + + + + GroupBox + + + + + + + + + AbsValue: + + + + + + + false + + + 100 + + + 50 + + + Qt::Vertical + + + + + + + QLayout::SetMinimumSize + + + + + false + + + *3 + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 23 + + + + + + + + false + + + 100% + + + + + + + Qt::Vertical + + + QSizePolicy::Preferred + + + + 20 + 23 + + + + + + + + false + + + /3 + + + + + + + + + + 0 + 0 + + + + + + + + RandValue: + + + + + + + + 0 + 0 + + + + + + + + Frequency: + + + + + + + + 0 + 0 + + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.cpp new file mode 100644 index 000000000..c4e4c6a15 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.cpp @@ -0,0 +1,108 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_noise_value_widget.h" + +// NeL includes + +#define NL_VEGETABLE_EDIT_SLIDER_NVS_SIZE_THRESHOLD 0.03f +#define NL_VEGETABLE_EDIT_SLIDER_NVS_SCALE 3.0f + +namespace NLQT { + +CVegetNoiseValueWidget::CVegetNoiseValueWidget(QWidget *parent) + : QGroupBox(parent), _emit(true) +{ + _ui.setupUi(this); + + connect(_ui.verticalSlider, SIGNAL(valueChanged(int)), this, SLOT(setSlider(int))); + connect(_ui.verticalSlider, SIGNAL(sliderReleased()), this, SLOT(sliderReleased())); + connect(_ui.absWidget, SIGNAL(valueChanged(float)), this, SLOT(setAbsValue(float))); + connect(_ui.randWidget, SIGNAL(valueChanged(float)), this, SLOT(setRandValue(float))); + connect(_ui.freqWidget, SIGNAL(valueChanged(float)), this, SLOT(setFreqValue(float))); + + setDefaultRangeAbs(0, 1); + setDefaultRangeRand(0, 1); + setDefaultRangeFreq(0, 1); + + setNoiseValue(NLMISC::CNoiseValue(), false); +} + +CVegetNoiseValueWidget::~CVegetNoiseValueWidget() +{ +} + +void CVegetNoiseValueWidget::setNoiseValue(const NLMISC::CNoiseValue &value, bool emit) +{ + _emit = false; + _ui.absWidget->setValue(value.Abs); + _ui.randWidget->setValue(value.Rand); + _ui.freqWidget->setValue(value.Frequency); + _emit = true; + if (emit) + noiseValueChanged(_NoiseValue); +} + +void CVegetNoiseValueWidget::setDefaultRangeAbs(float defRangeMin, float defRangeMax) +{ + _ui.absWidget->setRange(defRangeMin, defRangeMax); +} + +void CVegetNoiseValueWidget::setDefaultRangeRand(float defRangeMin, float defRangeMax) +{ + _ui.randWidget->setRange(defRangeMin, defRangeMax); +} + +void CVegetNoiseValueWidget::setDefaultRangeFreq(float defRangeMin, float defRangeMax) +{ + _ui.freqWidget->setRange(defRangeMin, defRangeMax); +} + +void CVegetNoiseValueWidget::setSlider(int value) +{ +} + +void CVegetNoiseValueWidget::sliderReleased() +{ +} + +void CVegetNoiseValueWidget::setAbsValue(float value) +{ + _NoiseValue.Abs = value; + if (_emit) + noiseValueChanged(_NoiseValue); +} + +void CVegetNoiseValueWidget::setRandValue(float value) +{ + _NoiseValue.Rand = value; + if (_emit) + noiseValueChanged(_NoiseValue); + +} + +void CVegetNoiseValueWidget::setFreqValue(float value) +{ + _NoiseValue.Frequency = value; + if (_emit) + noiseValueChanged(_NoiseValue); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.h new file mode 100644 index 000000000..8f715851b --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_noise_value_widget.h @@ -0,0 +1,69 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_NOISE_VALUE_WIDGET_H +#define VEGETABLE_NOISE_VALUE_WIDGET_H + +#include "ui_vegetable_noise_value_form.h" + +// STL includes + +// NeL includes +#include + +// Project includes + +namespace NLQT { + +class CVegetNoiseValueWidget: public QGroupBox +{ + Q_OBJECT + +public: + CVegetNoiseValueWidget(QWidget *parent = 0); + ~CVegetNoiseValueWidget(); + + void setNoiseValue(const NLMISC::CNoiseValue &value, bool emit = true); + + void setDefaultRangeAbs(float defRangeMin, float defRangeMax); + void setDefaultRangeRand(float defRangeMin, float defRangeMax); + void setDefaultRangeFreq(float defRangeMin, float defRangeMax); + +Q_SIGNALS: + void noiseValueChanged(NLMISC::CNoiseValue value); + +private Q_SLOTS: + void setSlider(int value); + void sliderReleased(); + void setAbsValue(float value); + void setRandValue(float value); + void setFreqValue(float value); + +private: + NLMISC::CNoiseValue _NoiseValue; + + bool _emit; + + Ui::CVegetNoiseValueWidget _ui; + +}; /* class CVegetNoiseValueWidget */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_NOISE_VALUE_WIDGET_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_form.ui b/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_form.ui new file mode 100644 index 000000000..825b5a860 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_form.ui @@ -0,0 +1,63 @@ + + + CVegetableRotatePage + + + + 0 + 0 + 204 + 223 + + + + Form + + + + + + Rotate X + + + + + + + Rotate Y + + + + + + + Rotate Z + + + + + + + Qt::Vertical + + + + 20 + 158 + + + + + + + + + NLQT::CVegetNoiseValueWidget + QGroupBox +
vegetable_noise_value_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.cpp new file mode 100644 index 000000000..73a49597d --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.cpp @@ -0,0 +1,93 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_rotate_page.h" + +// NeL includes +#include + +// Projects include +#include "modules.h" + +namespace NLQT { + +CVegetableRotatePage::CVegetableRotatePage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.rotateXGroupBox->setDefaultRangeAbs(NL_VEGETABLE_ROTATE_RANGE_MIN, NL_VEGETABLE_ROTATE_RANGE_MAX); + _ui.rotateXGroupBox->setDefaultRangeRand(NL_VEGETABLE_ROTATE_RANGE_MIN, NL_VEGETABLE_ROTATE_RANGE_MAX); + _ui.rotateXGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + _ui.rotateYGroupBox->setDefaultRangeAbs(NL_VEGETABLE_ROTATE_RANGE_MIN, NL_VEGETABLE_ROTATE_RANGE_MAX); + _ui.rotateYGroupBox->setDefaultRangeRand(NL_VEGETABLE_ROTATE_RANGE_MIN, NL_VEGETABLE_ROTATE_RANGE_MAX); + _ui.rotateYGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + _ui.rotateZGroupBox->setDefaultRangeAbs(NL_VEGETABLE_ROTATE_RANGE_MIN, NL_VEGETABLE_ROTATE_RANGE_MAX); + _ui.rotateZGroupBox->setDefaultRangeRand(NL_VEGETABLE_ROTATE_RANGE_MIN, NL_VEGETABLE_ROTATE_RANGE_MAX); + _ui.rotateZGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + setEnabled(false); + + connect(_ui.rotateXGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueRotateX(NLMISC::CNoiseValue))); + connect(_ui.rotateYGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueRotateX(NLMISC::CNoiseValue))); + connect(_ui.rotateZGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueRotateZ(NLMISC::CNoiseValue))); +} + +CVegetableRotatePage::~CVegetableRotatePage() +{ +} + +void CVegetableRotatePage::setVegetableToEdit(NL3D::CVegetable *vegetable) +{ + _Vegetable = vegetable; + + if(_Vegetable) + { + setEnabled(true); + + _ui.rotateXGroupBox->setNoiseValue(_Vegetable->Rx, false); + _ui.rotateYGroupBox->setNoiseValue(_Vegetable->Ry, false); + _ui.rotateZGroupBox->setNoiseValue(_Vegetable->Rz, false); + } + else + setEnabled(false); +} + +void CVegetableRotatePage::setNoiseValueRotateX(NLMISC::CNoiseValue value) +{ + _Vegetable->Rx = value; + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableRotatePage::setNoiseValueRotateY(NLMISC::CNoiseValue value) +{ + _Vegetable->Ry = value; + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableRotatePage::setNoiseValueRotateZ(NLMISC::CNoiseValue value) +{ + _Vegetable->Rz = value; + Modules::veget().refreshVegetableDisplay(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.h new file mode 100644 index 000000000..21f1e7414 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_rotate_page.h @@ -0,0 +1,64 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_ROTATE_PAGE_H +#define VEGETABLE_ROTATE_PAGE_H + +#include "ui_vegetable_rotate_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NL3D +{ + class CVegetable; +} + +namespace NLQT { + +class CVegetableRotatePage: public QWidget +{ + Q_OBJECT + +public: + CVegetableRotatePage(QWidget *parent = 0); + ~CVegetableRotatePage(); + + /// set the vegetble to edit. NULL will disable all the controls. + void setVegetableToEdit(NL3D::CVegetable *vegetable); + +private Q_SLOTS: + void setNoiseValueRotateX(NLMISC::CNoiseValue value); + void setNoiseValueRotateY(NLMISC::CNoiseValue value); + void setNoiseValueRotateZ(NLMISC::CNoiseValue value); + +private: + + // The vegetable to edit. + NL3D::CVegetable *_Vegetable; + + Ui::CVegetableRotatePage _ui; + +}; /* class CVegetableRotatePage */ + +} /* namespace NLQT */ +#endif // VEGETABLE_ROTATE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_form.ui b/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_form.ui new file mode 100644 index 000000000..a78c16abd --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_form.ui @@ -0,0 +1,74 @@ + + + CVegetableScalePage + + + + 0 + 0 + 194 + 237 + + + + Form + + + + + + Scale X/Y + + + + + + + Scale Z + + + + + + + Bend Frequency Factor + + + + + + + + + + + + Qt::Vertical + + + + 20 + 172 + + + + + + + + + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CVegetNoiseValueWidget + QGroupBox +
vegetable_noise_value_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.cpp b/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.cpp new file mode 100644 index 000000000..5d429e631 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.cpp @@ -0,0 +1,92 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "vegetable_scale_page.h" + +// NeL includes +#include + +// Projects include +#include "modules.h" + +namespace NLQT { + +CVegetableScalePage::CVegetableScalePage(QWidget *parent) + : QWidget(parent) +{ + _ui.setupUi(this); + + _ui.scaleXYGroupBox->setDefaultRangeAbs(NL_VEGETABLE_SCALE_RANGE_MIN, NL_VEGETABLE_SCALE_RANGE_MAX); + _ui.scaleXYGroupBox->setDefaultRangeRand(NL_VEGETABLE_SCALE_RANGE_MIN, NL_VEGETABLE_SCALE_RANGE_MAX); + _ui.scaleXYGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + _ui.scaleZGroupBox->setDefaultRangeAbs(NL_VEGETABLE_SCALE_RANGE_MIN, NL_VEGETABLE_SCALE_RANGE_MAX); + _ui.scaleZGroupBox->setDefaultRangeRand(NL_VEGETABLE_SCALE_RANGE_MIN, NL_VEGETABLE_SCALE_RANGE_MAX); + _ui.scaleZGroupBox->setDefaultRangeFreq(NL_VEGETABLE_FREQ_RANGE_MIN, NL_VEGETABLE_FREQ_RANGE_MAX); + + _ui.freqFactorWidget->setRange(NL_VEGETABLE_BENDFREQ_RANGE_MIN, NL_VEGETABLE_BENDFREQ_RANGE_MAX); + + setEnabled(false); + + connect(_ui.scaleXYGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueXYSize(NLMISC::CNoiseValue))); + connect(_ui.scaleZGroupBox, SIGNAL(noiseValueChanged(NLMISC::CNoiseValue)), this, SLOT(setNoiseValueXYSize(NLMISC::CNoiseValue))); + connect(_ui.freqFactorWidget, SIGNAL(valueChanged(float)), this, SLOT(setFreqFactor(float))); +} + +CVegetableScalePage::~CVegetableScalePage() +{ +} + +void CVegetableScalePage::setVegetableToEdit(NL3D::CVegetable *vegetable) +{ + _Vegetable = vegetable; + + if(_Vegetable) + { + setEnabled(true); + + _ui.scaleXYGroupBox->setNoiseValue(_Vegetable->Sxy, false); + _ui.scaleZGroupBox->setNoiseValue(_Vegetable->Sz, false); + + _ui.freqFactorWidget->setValue(_Vegetable->BendFrequencyFactor, false); + } + else + setEnabled(false); +} + +void CVegetableScalePage::setNoiseValueXYSize(NLMISC::CNoiseValue value) +{ + _Vegetable->Sxy = value; + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableScalePage::setNoiseValueZSize(NLMISC::CNoiseValue value) +{ + _Vegetable->Sz = value; + Modules::veget().refreshVegetableDisplay(); +} + +void CVegetableScalePage::setFreqFactor(float value) +{ + _Vegetable->BendFrequencyFactor = value; + Modules::veget().refreshVegetableDisplay(); +} + +} /* namespace NLQT */ \ No newline at end of file diff --git a/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.h b/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.h new file mode 100644 index 000000000..4181f0e41 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/vegetable_scale_page.h @@ -0,0 +1,65 @@ +/* + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef VEGETABLE_SCALE_PAGE_H +#define VEGETABLE_SCALE_PAGE_H + +#include "ui_vegetable_scale_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NL3D +{ + class CVegetable; +} + +namespace NLQT { + +class CVegetableScalePage: public QWidget +{ + Q_OBJECT + +public: + CVegetableScalePage(QWidget *parent = 0); + ~CVegetableScalePage(); + + /// set the vegetble to edit. NULL will disable all the controls. + void setVegetableToEdit(NL3D::CVegetable *vegetable); + +private Q_SLOTS: + void setNoiseValueXYSize(NLMISC::CNoiseValue value); + void setNoiseValueZSize(NLMISC::CNoiseValue value); + void setFreqFactor(float value); + +private: + + // The vegetable to edit. + NL3D::CVegetable *_Vegetable; + + Ui::CVegetableScalePage _ui; + +}; /* class CVegetableScalePage */ + +} /* namespace NLQT */ + +#endif // VEGETABLE_SCALE_PAGE_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.cpp b/code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.cpp new file mode 100644 index 000000000..00a02e93a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.cpp @@ -0,0 +1,35 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "stdpch.h" +#include "water_pool_dialog.h" + +namespace NLQT { + +CWaterPoolDialog::CWaterPoolDialog(QWidget *parent) + : QDockWidget(parent) +{ + ui.setupUi(this); +} + +CWaterPoolDialog::~CWaterPoolDialog() +{ +} + +} /* namespace NLQT */ diff --git a/code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.h b/code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.h new file mode 100644 index 000000000..46319dc9a --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/water_pool_dialog.h @@ -0,0 +1,51 @@ +/* + Object Viewer Qt + Copyright (C) 2010 Dzmitry Kamiahin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef WATER_POOL_DIALOG_H +#define WATER_POOL_DIALOG_H + +#include "ui_water_pool_form.h" + +// STL includes + +// NeL includes + +// Project includes + +namespace NLQT { + +class CWaterPoolDialog: public QDockWidget +{ + Q_OBJECT + +public: + CWaterPoolDialog(QWidget *parent = 0); + ~CWaterPoolDialog(); + +private Q_SLOTS: + +private: + + Ui::CWaterPoolDialog ui; + +}; /* class CWaterPoolDialog */ + +} /* namespace NLQT */ + +#endif // WATER_POOL_DIALOG_H diff --git a/code/nel/tools/3d/object_viewer_qt/src/water_pool_form.ui b/code/nel/tools/3d/object_viewer_qt/src/water_pool_form.ui new file mode 100644 index 000000000..f1103a7bd --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/water_pool_form.ui @@ -0,0 +1,281 @@ + + + CWaterPoolDialog + + + + 0 + 0 + 390 + 690 + + + + Water pool editor + + + + + + + Qt::Horizontal + + + + + QLayout::SetMinimumSize + + + + + Pool ID List: + + + + + + + + + + Add + + + + + + + Delete + + + + + + + Name... + + + + + + + + + + + + + Size: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16 + + + + + 32 + + + + + 64 + + + + + 128 + + + + + 256 + + + + + 512 + + + + + + + + + + Water Unit Size: + + + + + + + + + + Damping: + + + + + + + + + + Filter weight: + + + + + + + + + + Propagation time: + + + + + + + + + + Enable automatic waves generation + + + + + + + Waves + + + + + + Impulsion strenght: + + + + + + + + + + Period: + + + + + + + + + + Impulsion radius: + + + + + + + + + + Generate on border only + + + + + + + + + + + + Load + + + + + + + Save + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + NLQT::CEditRangeUIntWidget + QWidget +
edit_range_widget.h
+ 1 +
+ + NLQT::CEditRangeFloatWidget + QWidget +
edit_range_widget.h
+ 1 +
+
+ + +
diff --git a/code/nel/tools/3d/object_viewer_qt/src/workspace_form.ui b/code/nel/tools/3d/object_viewer_qt/src/workspace_form.ui new file mode 100644 index 000000000..640cda6c2 --- /dev/null +++ b/code/nel/tools/3d/object_viewer_qt/src/workspace_form.ui @@ -0,0 +1,456 @@ + + + CWorkspacePage + + + + 0 + 0 + 258 + 246 + + + + Form + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 11 + 75 + false + true + true + + + + 0 + + + Particle system editor + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Workspace: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 55 + 55 + + + + + 55 + 55 + + + + New + + + + :/images/new.png:/images/new.png + + + + 32 + 32 + + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + + 55 + 55 + + + + + 55 + 55 + + + + Load + + + + :/images/open-file.png:/images/open-file.png + + + + 32 + 32 + + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + false + + + + 55 + 55 + + + + + 55 + 55 + + + + Save + + + + :/images/save.png:/images/save.png + + + + 32 + 32 + + + + false + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + false + + + + 55 + 55 + + + + + 55 + 55 + + + + Save As + + + + :/images/save-as.png:/images/save-as.png + + + + 32 + 32 + + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Particle system: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + false + + + + 70 + 65 + + + + + 61 + 61 + + + + Insert New + + + + :/images/pqrticles.png:/images/pqrticles.png + + + + 32 + 32 + + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + false + + + + 70 + 65 + + + + + 61 + 61 + + + + Create New + + + + :/images/insert.png:/images/insert.png + + + + 32 + 32 + + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + false + + + + 70 + 65 + + + + + 61 + 61 + + + + Reset all + + + + :/images/clear.png:/images/clear.png + + + + 32 + 32 + + + + Qt::ToolButtonTextUnderIcon + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 3 + + + + + + + + + + +