diff --git a/resources/profiles/Creality.json b/resources/profiles/Creality.json index 894988cbd40..776682a2c10 100644 --- a/resources/profiles/Creality.json +++ b/resources/profiles/Creality.json @@ -494,6 +494,14 @@ "name": "0.24mm Draft @Creality Ender3V2Neo", "sub_path": "process/0.24mm Draft @Creality Ender3V2Neo.json" }, + { + "name": "0.24mm Draft @Creality Ender3V3SE", + "sub_path": "process/0.24mm Draft @Creality Ender3V3SE.json" + }, + { + "name": "0.24mm Draft @Creality Ender3V3KE", + "sub_path": "process/0.24mm Draft @Creality Ender3V3KE.json" + }, { "name": "0.24mm Draft @Creality Ender5Pro (2019) 0.3", "sub_path": "process/0.24mm Draft @Creality Ender5Pro (2019) 0.3.json" diff --git a/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3KE.json b/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3KE.json index 26c440b4764..a00e9b7d8cf 100644 --- a/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3KE.json +++ b/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3KE.json @@ -15,7 +15,7 @@ "bridge_flow": "0.95", "bridge_speed": "100", "brim_type": "no_brim", - "brim_width": "0", + "brim_width": "5", "brim_object_gap": "0.1", "compatible_printers_condition": "", "print_sequence": "by layer", diff --git a/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3SE.json b/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3SE.json index 1aaf17848a9..95746c0bd7f 100644 --- a/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3SE.json +++ b/resources/profiles/Creality/process/0.12mm Fine @Creality Ender3V3SE.json @@ -15,7 +15,7 @@ "bridge_flow": "0.95", "bridge_speed": "100", "brim_type": "no_brim", - "brim_width": "0", + "brim_width": "5", "brim_object_gap": "0.1", "compatible_printers_condition": "", "print_sequence": "by layer", diff --git a/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3KE.json b/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3KE.json index 1d4f167023d..0668dc00c9b 100644 --- a/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3KE.json +++ b/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3KE.json @@ -15,7 +15,7 @@ "bridge_flow": "0.95", "bridge_speed": "100", "brim_type": "no_brim", - "brim_width": "0", + "brim_width": "5", "brim_object_gap": "0.1", "compatible_printers_condition": "", "print_sequence": "by layer", diff --git a/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3SE.json b/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3SE.json index 06f8a0ca989..925693ba2cf 100644 --- a/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3SE.json +++ b/resources/profiles/Creality/process/0.16mm Optimal @Creality Ender3V3SE.json @@ -15,7 +15,7 @@ "bridge_flow": "0.95", "bridge_speed": "100", "brim_type": "no_brim", - "brim_width": "0", + "brim_width": "5", "brim_object_gap": "0.1", "compatible_printers_condition": "", "print_sequence": "by layer", diff --git a/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3KE.json b/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3KE.json index 82b6a978ce6..3ebb10711b0 100644 --- a/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3KE.json +++ b/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3KE.json @@ -15,7 +15,7 @@ "bridge_flow": "0.95", "bridge_speed": "100", "brim_type": "no_brim", - "brim_width": "0", + "brim_width": "5", "brim_object_gap": "0.1", "compatible_printers_condition": "", "print_sequence": "by layer", diff --git a/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3SE.json b/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3SE.json index d4c6979f515..acb5f272fc7 100644 --- a/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3SE.json +++ b/resources/profiles/Creality/process/0.20mm Standard @Creality Ender3V3SE.json @@ -15,7 +15,7 @@ "bridge_flow": "0.95", "bridge_speed": "100", "brim_type": "no_brim", - "brim_width": "0", + "brim_width": "5", "brim_object_gap": "0.1", "compatible_printers_condition": "", "print_sequence": "by layer", diff --git a/resources/profiles/Creality/process/0.24mm Draft @Creality Ender3V3KE.json b/resources/profiles/Creality/process/0.24mm Draft @Creality Ender3V3KE.json new file mode 100644 index 00000000000..93b1118672a --- /dev/null +++ b/resources/profiles/Creality/process/0.24mm Draft @Creality Ender3V3KE.json @@ -0,0 +1,117 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.24mm Draft @Creality Ender3V3KE", + "from": "system", + "inherits": "fdm_process_common_klipper", + "instantiation": "true", + "adaptive_layer_height": "1", + "reduce_crossing_wall": "0", + "layer_height": "0.24", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "4", + "bottom_shell_thickness": "0", + "bridge_flow": "0.95", + "bridge_speed": "100", + "brim_type": "no_brim", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers_condition": "", + "print_sequence": "by layer", + "default_acceleration": "5000", + "top_surface_acceleration": "5000", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0", + "enable_arc_fitting": "0", + "outer_wall_line_width": "0.4", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.4", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "grid", + "initial_layer_acceleration": "500", + "travel_acceleration": "5000", + "inner_wall_acceleration": "5000", + "outer_wall_acceleration": "2000", + "initial_layer_line_width": "0.5", + "initial_layer_print_height": "0.2", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "25%", + "interface_shells": "0", + "ironing_flow": "15%", + "ironing_spacing": "0.1", + "ironing_speed": "44%", + "ironing_type": "no ironing", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "20", + "overhang_3_4_speed": "15", + "overhang_4_4_speed": "10", + "inner_wall_line_width": "0.42", + "wall_loops": "2", + "print_settings_id": "", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "2", + "skirt_loops": "0", + "minimum_sparse_infill_area": "10", + "internal_solid_infill_line_width": "0", + "spiral_mode": "0", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "grid", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.15", + "support_filament": "0", + "support_line_width": "0.38", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "3", + "support_interface_bottom_layers": "-1", + "support_interface_spacing": "0.2", + "support_interface_speed": "100%", + "support_base_pattern": "rectilinear", + "support_base_pattern_spacing": "0.2", + "support_speed": "200", + "support_threshold_angle": "45", + "support_object_xy_distance": "60%", + "tree_support_branch_angle": "40", + "tree_support_wall_count": "0", + "detect_thin_wall": "1", + "top_surface_pattern": "monotonic", + "top_surface_line_width": "0.4", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "initial_layer_speed": "50", + "initial_layer_infill_speed": "105", + "outer_wall_speed": "200", + "inner_wall_speed": "300", + "internal_solid_infill_speed": "300", + "top_surface_speed": "200", + "gap_infill_speed": "100", + "sparse_infill_speed": "300", + "travel_speed": "300", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "default_jerk": "30", + "outer_wall_jerk": "30", + "inner_wall_jerk": "30", + "infill_jerk": "30", + "top_surface_jerk": "15", + "initial_layer_jerk": "15", + "travel_jerk": "30", + "compatible_printers": [ + "Creality Ender-3 V3 KE 0.4 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/Creality/process/0.24mm Draft @Creality Ender3V3SE.json b/resources/profiles/Creality/process/0.24mm Draft @Creality Ender3V3SE.json new file mode 100644 index 00000000000..3c51628d7d1 --- /dev/null +++ b/resources/profiles/Creality/process/0.24mm Draft @Creality Ender3V3SE.json @@ -0,0 +1,118 @@ +{ + "type": "process", + "setting_id": "GP004", + "name": "0.24mm Draft @Creality Ender3V3SE", + "from": "system", + "inherits": "fdm_process_creality_common", + "instantiation": "true", + "adaptive_layer_height": "1", + "reduce_crossing_wall": "0", + "layer_height": "0.24", + "max_travel_detour_distance": "0", + "bottom_surface_pattern": "monotonic", + "bottom_shell_layers": "4", + "bottom_shell_thickness": "0", + "bridge_flow": "0.95", + "bridge_speed": "100", + "brim_type": "no_brim", + "brim_width": "5", + "brim_object_gap": "0.1", + "compatible_printers_condition": "", + "print_sequence": "by layer", + "default_acceleration": "2500", + "top_surface_acceleration": "2500", + "bridge_no_support": "0", + "draft_shield": "disabled", + "elefant_foot_compensation": "0", + "enable_arc_fitting": "0", + "outer_wall_line_width": "0.42", + "wall_infill_order": "inner wall/outer wall/infill", + "line_width": "0.46", + "infill_direction": "45", + "sparse_infill_density": "15%", + "sparse_infill_pattern": "grid", + "initial_layer_acceleration": "500", + "travel_acceleration": "2500", + "inner_wall_acceleration": "2000", + "outer_wall_acceleration": "1000", + "initial_layer_line_width": "0.46", + "initial_layer_print_height": "0.2", + "infill_combination": "0", + "sparse_infill_line_width": "0.45", + "infill_wall_overlap": "15%", + "interface_shells": "0", + "ironing_flow": "15%", + "ironing_spacing": "0.1", + "ironing_speed": "20", + "ironing_type": "no ironing", + "reduce_infill_retraction": "1", + "filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode", + "detect_overhang_wall": "1", + "overhang_1_4_speed": "0", + "overhang_2_4_speed": "20", + "overhang_3_4_speed": "15", + "overhang_4_4_speed": "10", + "inner_wall_line_width": "0.45", + "wall_loops": "2", + "print_settings_id": "", + "raft_layers": "0", + "seam_position": "aligned", + "skirt_distance": "2", + "skirt_height": "2", + "skirt_loops": "0", + "minimum_sparse_infill_area": "10", + "internal_solid_infill_line_width": "0", + "spiral_mode": "0", + "standby_temperature_delta": "-5", + "enable_support": "0", + "resolution": "0.012", + "support_type": "normal(auto)", + "support_style": "grid", + "support_on_build_plate_only": "0", + "support_top_z_distance": "0.15", + "support_filament": "0", + "support_line_width": "0.38", + "support_interface_loop_pattern": "0", + "support_interface_filament": "0", + "support_interface_top_layers": "2", + "support_interface_bottom_layers": "2", + "support_interface_spacing": "0.5", + "support_interface_speed": "100%", + "support_interface_pattern": "auto", + "support_base_pattern": "rectilinear", + "support_base_pattern_spacing": "2.5", + "support_speed": "50", + "support_threshold_angle": "25", + "support_object_xy_distance": "60%", + "tree_support_branch_angle": "40", + "tree_support_wall_count": "2", + "detect_thin_wall": "1", + "top_surface_pattern": "monotonic", + "top_surface_line_width": "0.42", + "top_shell_layers": "4", + "top_shell_thickness": "0.8", + "initial_layer_speed": "30", + "initial_layer_infill_speed": "80", + "outer_wall_speed": "60", + "inner_wall_speed": "90", + "internal_solid_infill_speed": "180", + "top_surface_speed": "50", + "gap_infill_speed": "50", + "sparse_infill_speed": "180", + "travel_speed": "150", + "enable_prime_tower": "0", + "wipe_tower_no_sparse_layers": "0", + "prime_tower_width": "60", + "xy_hole_compensation": "0", + "xy_contour_compensation": "0", + "default_jerk": "8", + "outer_wall_jerk": "20", + "inner_wall_jerk": "20", + "infill_jerk": "20", + "top_surface_jerk": "20", + "initial_layer_jerk": "8", + "travel_jerk": "8", + "compatible_printers": [ + "Creality Ender-3 V3 SE 0.4 nozzle" + ] +} \ No newline at end of file diff --git a/resources/profiles/FlyingBear/filament/FlyingBear Generic PA-CF.json b/resources/profiles/FlyingBear/filament/FlyingBear Generic PA-CF.json index 7a59fc58751..faf2f476cfa 100644 --- a/resources/profiles/FlyingBear/filament/FlyingBear Generic PA-CF.json +++ b/resources/profiles/FlyingBear/filament/FlyingBear Generic PA-CF.json @@ -9,15 +9,24 @@ "filament_type": [ "PA-CF" ], - "nozzle_temperature_initial_layer": [ - "280" - ], - "nozzle_temperature": [ - "280" - ], "filament_max_volumetric_speed": [ "8" ], + "filament_flow_ratio": [ + "0.96" + ], + "filament_wipe": [ + "1" + ], + "filament_wipe_distance": [ + "4" + ], + "filament_retract_before_wipe": [ + "0%" + ], + "filament_retraction_length": [ + "0.8" + ], "compatible_printers": [ "FlyingBear Reborn3 0.4 nozzle" diff --git a/resources/profiles/FlyingBear/filament/FlyingBear Generic PC.json b/resources/profiles/FlyingBear/filament/FlyingBear Generic PC.json index d8486c08e34..f60cff0f63b 100644 --- a/resources/profiles/FlyingBear/filament/FlyingBear Generic PC.json +++ b/resources/profiles/FlyingBear/filament/FlyingBear Generic PC.json @@ -10,7 +10,13 @@ "12" ], "filament_flow_ratio": [ - "0.931" + "0.94" + ], + "enable_pressure_advance": [ + "1" + ], + "pressure_advance": [ + "0.058" ], "compatible_printers": [ "FlyingBear Reborn3 0.4 nozzle" diff --git a/resources/profiles/FlyingBear/filament/FlyingBear Generic PETG.json b/resources/profiles/FlyingBear/filament/FlyingBear Generic PETG.json index 1257a0c6cdf..a02f0f5c9ad 100644 --- a/resources/profiles/FlyingBear/filament/FlyingBear Generic PETG.json +++ b/resources/profiles/FlyingBear/filament/FlyingBear Generic PETG.json @@ -19,7 +19,7 @@ "90" ], "overhang_fan_threshold": [ - "25%" + "10%" ], "fan_max_speed": [ "90" @@ -34,7 +34,7 @@ "8" ], "filament_flow_ratio": [ - "0.931" + "0.94" ], "filament_max_volumetric_speed": [ "12" @@ -42,6 +42,12 @@ "filament_start_gcode": [ "; filament start gcode\n" ], + "enable_pressure_advance": [ + "1" + ], + "pressure_advance": [ + "0.084" + ], "compatible_printers": [ "FlyingBear Reborn3 0.4 nozzle" ] diff --git a/resources/profiles/FlyingBear/filament/FlyingBear Generic PLA.json b/resources/profiles/FlyingBear/filament/FlyingBear Generic PLA.json index ef7dcfc96ac..f873512b357 100644 --- a/resources/profiles/FlyingBear/filament/FlyingBear Generic PLA.json +++ b/resources/profiles/FlyingBear/filament/FlyingBear Generic PLA.json @@ -7,14 +7,17 @@ "instantiation": "true", "inherits": "fdm_filament_pla", "filament_flow_ratio": [ - "0.931" + "0.94" ], "filament_max_volumetric_speed": [ - "26" + "12" ], "slow_down_layer_time": [ "8" ], + "pressure_advance": [ + "0.084" + ], "compatible_printers": [ "FlyingBear Reborn3 0.4 nozzle" ] diff --git a/resources/profiles/FlyingBear/filament/FlyingBear Generic TPU.json b/resources/profiles/FlyingBear/filament/FlyingBear Generic TPU.json index a2fe4bff860..f369b553942 100644 --- a/resources/profiles/FlyingBear/filament/FlyingBear Generic TPU.json +++ b/resources/profiles/FlyingBear/filament/FlyingBear Generic TPU.json @@ -7,7 +7,16 @@ "instantiation": "true", "inherits": "fdm_filament_tpu", "filament_max_volumetric_speed": [ - "3.2" + "3" + ], + "filament_flow_ratio": [ + "0.94" + ], + "enable_pressure_advance": [ + "1" + ], + "pressure_advance": [ + "0.13" ], "compatible_printers": [ "FlyingBear Reborn3 0.4 nozzle" diff --git a/resources/profiles/FlyingBear/filament/FlyingBear PLA Hyper.json b/resources/profiles/FlyingBear/filament/FlyingBear PLA Hyper.json new file mode 100644 index 00000000000..82a1e11c275 --- /dev/null +++ b/resources/profiles/FlyingBear/filament/FlyingBear PLA Hyper.json @@ -0,0 +1,27 @@ +{ + "type": "filament", + "filament_id": "GFL99", + "setting_id": "GFSA04", + "name": "FlyingBear PLA Hyper", + "from": "system", + "instantiation": "true", + "inherits": "fdm_filament_pla_Hyper", + "filament_flow_ratio": [ + "0.94" + ], + "filament_max_volumetric_speed": [ + "25" + ], + "slow_down_layer_time": [ + "8" + ], + "enable_pressure_advance": [ + "1" + ], + "pressure_advance": [ + "0.02" + ], + "compatible_printers": [ + "FlyingBear Reborn3 0.4 nozzle" + ] +} diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_common.json b/resources/profiles/FlyingBear/filament/fdm_filament_common.json index e8244c65c41..b3f613e8ec1 100644 --- a/resources/profiles/FlyingBear/filament/fdm_filament_common.json +++ b/resources/profiles/FlyingBear/filament/fdm_filament_common.json @@ -28,7 +28,7 @@ "60" ], "overhang_fan_threshold": [ - "95%" + "0%" ], "overhang_fan_speed": [ "100" diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_pa.json b/resources/profiles/FlyingBear/filament/fdm_filament_pa.json index cbfa23c1000..79003f41294 100644 --- a/resources/profiles/FlyingBear/filament/fdm_filament_pa.json +++ b/resources/profiles/FlyingBear/filament/fdm_filament_pa.json @@ -4,6 +4,9 @@ "from": "system", "instantiation": "false", "inherits": "fdm_filament_common", + "filament_vendor": [ + "FlyingBear" + ], "cool_plate_temp" : [ "0" ], @@ -14,19 +17,19 @@ "110" ], "textured_plate_temp" : [ - "100" + "110" ], "cool_plate_temp_initial_layer" : [ "0" ], "eng_plate_temp_initial_layer" : [ - "100" + "110" ], "hot_plate_temp_initial_layer" : [ - "100" + "110" ], "textured_plate_temp_initial_layer" : [ - "100" + "110" ], "slow_down_for_layer_cooling": [ "1" @@ -50,7 +53,7 @@ "20" ], "nozzle_temperature_initial_layer": [ - "280" + "240" ], "reduce_fan_stop_start_freq": [ "0" @@ -65,20 +68,26 @@ "40" ], "nozzle_temperature": [ - "280" + "240" ], "temperature_vitrification": [ "108" ], "nozzle_temperature_range_low": [ - "270" + "240" ], "nozzle_temperature_range_high": [ - "300" + "280" ], "slow_down_min_speed": [ "20" ], + "activate_air_filtration": [ + "1" + ], + "complete_print_exhaust_fan_speed": [ + "60" + ], "slow_down_layer_time": [ "2" ] diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_pc.json b/resources/profiles/FlyingBear/filament/fdm_filament_pc.json index d75eeaf55dc..589ae19b4e8 100644 --- a/resources/profiles/FlyingBear/filament/fdm_filament_pc.json +++ b/resources/profiles/FlyingBear/filament/fdm_filament_pc.json @@ -4,6 +4,9 @@ "from": "system", "instantiation": "false", "inherits": "fdm_filament_common", + "filament_vendor": [ + "FlyingBear" + ], "cool_plate_temp" : [ "0" ], @@ -82,6 +85,12 @@ "slow_down_min_speed": [ "20" ], + "activate_air_filtration": [ + "1" + ], + "complete_print_exhaust_fan_speed": [ + "60" + ], "slow_down_layer_time": [ "2" ] diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_pet.json b/resources/profiles/FlyingBear/filament/fdm_filament_pet.json index 9b5735fa904..389942acf7e 100644 --- a/resources/profiles/FlyingBear/filament/fdm_filament_pet.json +++ b/resources/profiles/FlyingBear/filament/fdm_filament_pet.json @@ -4,6 +4,9 @@ "from": "system", "instantiation": "false", "inherits": "fdm_filament_common", + "filament_vendor": [ + "FlyingBear" + ], "cool_plate_temp" : [ "60" ], @@ -11,7 +14,7 @@ "0" ], "hot_plate_temp" : [ - "70" + "75" ], "textured_plate_temp" : [ "80" @@ -23,7 +26,7 @@ "0" ], "hot_plate_temp_initial_layer" : [ - "70" + "75" ], "textured_plate_temp_initial_layer" : [ "80" @@ -71,10 +74,13 @@ "80" ], "nozzle_temperature_range_low": [ - "220" + "200" ], "nozzle_temperature_range_high": [ - "260" + "280" + ], + "additional_cooling_fan_speed": [ + "100" ], "filament_start_gcode": [ "; filament start gcode\n" diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_pla.json b/resources/profiles/FlyingBear/filament/fdm_filament_pla.json index 8ef68484230..f1fe0ff99b5 100644 --- a/resources/profiles/FlyingBear/filament/fdm_filament_pla.json +++ b/resources/profiles/FlyingBear/filament/fdm_filament_pla.json @@ -4,11 +4,14 @@ "from": "system", "instantiation": "false", "inherits": "fdm_filament_common", + "filament_vendor": [ + "FlyingBear" + ], "fan_cooling_layer_time": [ - "80" + "100" ], "filament_max_volumetric_speed": [ - "26" + "12" ], "filament_type": [ "PLA" @@ -38,7 +41,7 @@ "60" ], "hot_plate_temp_initial_layer" : [ - "60" + "65" ], "textured_plate_temp_initial_layer" : [ "60" @@ -56,7 +59,7 @@ "100" ], "fan_min_speed": [ - "50" + "100" ], "overhang_fan_speed": [ "100" @@ -68,7 +71,7 @@ "1" ], "nozzle_temperature": [ - "200" + "195" ], "temperature_vitrification": [ "60" @@ -86,7 +89,7 @@ "8" ], "additional_cooling_fan_speed": [ - "70" + "100" ], "filament_start_gcode": [ "; filament start gcode\n" diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_pla_Hyper.json b/resources/profiles/FlyingBear/filament/fdm_filament_pla_Hyper.json new file mode 100644 index 00000000000..4a3150818ab --- /dev/null +++ b/resources/profiles/FlyingBear/filament/fdm_filament_pla_Hyper.json @@ -0,0 +1,97 @@ +{ + "type": "filament", + "name": "fdm_filament_pla_Hyper", + "from": "system", + "instantiation": "false", + "inherits": "fdm_filament_common", + "filament_vendor": [ + "FlyingBear" + ], + "fan_cooling_layer_time": [ + "100" + ], + "filament_max_volumetric_speed": [ + "12" + ], + "filament_type": [ + "PLA" + ], + "filament_density": [ + "1.24" + ], + "filament_cost": [ + "20" + ], + "cool_plate_temp" : [ + "60" + ], + "eng_plate_temp" : [ + "60" + ], + "hot_plate_temp" : [ + "60" + ], + "textured_plate_temp" : [ + "60" + ], + "cool_plate_temp_initial_layer" : [ + "60" + ], + "eng_plate_temp_initial_layer" : [ + "60" + ], + "hot_plate_temp_initial_layer" : [ + "65" + ], + "textured_plate_temp_initial_layer" : [ + "60" + ], + "nozzle_temperature_initial_layer": [ + "200" + ], + "reduce_fan_stop_start_freq": [ + "1" + ], + "slow_down_for_layer_cooling": [ + "1" + ], + "fan_max_speed": [ + "100" + ], + "fan_min_speed": [ + "100" + ], + "overhang_fan_speed": [ + "100" + ], + "overhang_fan_threshold": [ + "50%" + ], + "close_fan_the_first_x_layers": [ + "1" + ], + "nozzle_temperature": [ + "195" + ], + "temperature_vitrification": [ + "60" + ], + "nozzle_temperature_range_low": [ + "190" + ], + "nozzle_temperature_range_high": [ + "230" + ], + "slow_down_min_speed": [ + "20" + ], + "slow_down_layer_time": [ + "8" + ], + "additional_cooling_fan_speed": [ + "100" + ], + "filament_start_gcode": [ + "; filament start gcode\n" + ] +} diff --git a/resources/profiles/FlyingBear/filament/fdm_filament_tpu.json b/resources/profiles/FlyingBear/filament/fdm_filament_tpu.json index 012be60aba7..6a335be746d 100644 --- a/resources/profiles/FlyingBear/filament/fdm_filament_tpu.json +++ b/resources/profiles/FlyingBear/filament/fdm_filament_tpu.json @@ -4,6 +4,9 @@ "from": "system", "instantiation": "false", "inherits": "fdm_filament_common", + "filament_vendor": [ + "FlyingBear" + ], "cool_plate_temp" : [ "30" ], @@ -43,9 +46,7 @@ "filament_cost": [ "20" ], - "filament_retraction_length": [ - "0.4" - ], + "nozzle_temperature_initial_layer": [ "210" ], @@ -65,7 +66,7 @@ "100" ], "additional_cooling_fan_speed": [ - "70" + "100" ], "close_fan_the_first_x_layers": [ "1" @@ -82,6 +83,15 @@ "nozzle_temperature_range_high": [ "250" ], + "filament_z_hop": [ + "0" + ], + "slow_down_layer_time": [ + "10" + ], + "overhang_fan_threshold": [ + "95%" + ], "filament_start_gcode": [ "; filament start gcode\n" ] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71f6fe1f5f6..c8e63107a45 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,7 @@ add_subdirectory(libslic3r) if (SLIC3R_GUI) add_subdirectory(imgui) + add_subdirectory(imguizmo) add_subdirectory(hidapi) include_directories(hidapi/include) diff --git a/src/imguizmo/CMakeLists.txt b/src/imguizmo/CMakeLists.txt new file mode 100644 index 00000000000..fec44318059 --- /dev/null +++ b/src/imguizmo/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.8.12) +project(imguizmo) + +add_library(imguizmo STATIC + ImGuizmo.h + ImGuizmo.cpp +) + +target_link_libraries(imguizmo PRIVATE imgui) diff --git a/src/imguizmo/ImGuizmo.cpp b/src/imguizmo/ImGuizmo.cpp new file mode 100644 index 00000000000..20ec388cb6c --- /dev/null +++ b/src/imguizmo/ImGuizmo.cpp @@ -0,0 +1,3093 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include +#include +#include "ImGuizmo.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace IMGUIZMO_NAMESPACE +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + // scale a bit so translate axis do not touch when in universal + const float rotationDisplayFactor = 1.2f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) ); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)) != 0; } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + Style::Style() + { + // default values + TranslationLineThickness = 3.0f; + TranslationLineArrowSize = 6.0f; + RotationLineThickness = 2.0f; + RotationOuterLineThickness = 3.0f; + ScaleLineThickness = 3.0f; + ScaleLineCircleSize = 6.0f; + HatchedAxisLineThickness = 6.0f; + CenterCircleSize = 6.0f; + + // initialize default colors + Colors[DIRECTION_X] = ImVec4(0.666f, 0.000f, 0.000f, 1.000f); + Colors[DIRECTION_Y] = ImVec4(0.000f, 0.666f, 0.000f, 1.000f); + Colors[DIRECTION_Z] = ImVec4(0.000f, 0.000f, 0.666f, 1.000f); + Colors[PLANE_X] = ImVec4(0.666f, 0.000f, 0.000f, 0.380f); + Colors[PLANE_Y] = ImVec4(0.000f, 0.666f, 0.000f, 0.380f); + Colors[PLANE_Z] = ImVec4(0.000f, 0.000f, 0.666f, 0.380f); + Colors[SELECTION] = ImVec4(1.000f, 0.500f, 0.062f, 0.541f); + Colors[INACTIVE] = ImVec4(0.600f, 0.600f, 0.600f, 0.600f); + Colors[TRANSLATION_LINE] = ImVec4(0.666f, 0.666f, 0.666f, 0.666f); + Colors[SCALE_LINE] = ImVec4(0.250f, 0.250f, 0.250f, 1.000f); + Colors[ROTATION_USING_BORDER] = ImVec4(1.000f, 0.500f, 0.062f, 1.000f); + Colors[ROTATION_USING_FILL] = ImVec4(1.000f, 0.500f, 0.062f, 0.500f); + Colors[HATCHED_AXIS_LINES] = ImVec4(0.000f, 0.000f, 0.000f, 0.500f); + Colors[TEXT] = ImVec4(1.000f, 1.000f, 1.000f, 1.000f); + Colors[TEXT_SHADOW] = ImVec4(0.000f, 0.000f, 0.000f, 1.000f); + + strcpy(AxisLabels[Axis_X], "x"); + strcpy(AxisLabels[Axis_Y], "y"); + strcpy(AxisLabels[Axis_Z], "z"); + } + + struct Context + { + Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + Style mStyle; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelLocal; // orthonormalized model + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + bool mbMouseOver; + bool mReversed; // reversed projection matrix + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + float mAxisLimit=0.0025f; + float mPlaneLimit=0.02f; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + + int mActualID = -1; + int mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + Style& GetStyle() + { + return gContext.mStyle; + } + + static ImU32 GetColorU32(int idx) + { + IM_ASSERT(idx < COLOR::COUNT); + return ImGui::ColorConvertFloat4ToU32(gContext.mStyle.Colors[idx]); + } + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) + { + vec_t startOfSegment = start; + const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; + startOfSegment.TransformPoint(mvp); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(mvp); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + if (gContext.mDisplayRatio < 1.0) + clipSpaceAxis.x *= gContext.mDisplayRatio; + else + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + const float numer = plan.Dot3(rOrigin) - plan.w; + const float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(ImVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + static bool IsHoveringWindow() + { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName); + if (g.HoveredWindow == window) // Mouse hovering drawlist window + return true; + if (g.HoveredWindow != NULL) // Any other window is hovered + return false; + if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) + return true; + return false; + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(ImDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); + } + + void SetImGuiContext(ImGuiContext* ctx) + { + ImGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + + bool IsUsing() + { + return (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) || gContext.mbUsingBounds; + } + + bool IsUsingAny() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + gContext.mbMouseOver = IsHoveringWindow(); + + gContext.mModelLocal = *(matrix_t*)matrix; + gContext.mModelLocal.OrthoNormalize(); + + if (mode == LOCAL) + { + gContext.mModel = gContext.mModelLocal; + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + ImU32 selectionColor = GetColorU32(SELECTION); + + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : GetColorU32(PLANE_X + i); + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + case SCALEU: + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + ImU32 inactiveColor = GetColorU32(INACTIVE); + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirAxis *= gContext.mAxisFactor[axisIndex]; + dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; + } + else + { + // new method + float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis, localCoordinates); + float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis, localCoordinates); + + float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX, localCoordinates); + float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX, localCoordinates); + + float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY, localCoordinates); + float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY, localCoordinates); + + // For readability + bool & allowFlip = gContext.mAllowAxisFlip; + float mulAxis = (allowFlip && lenDir < lenDirMinus&& fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + belowPlaneLimit = (paraSurf > gContext.mAxisLimit); + belowAxisLimit = (axisLengthInClipSpace > gContext.mPlaneLimit); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxis; + gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; + gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } + } + + static void ComputeSnap(float* value, float snap) + { + if (snap <= FLT_EPSILON) + { + return; + } + + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + { + *value -= modulo; + } + else if (moduloRatio > (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + cameraToModelNormalized = -viewInverse.v.dir; + } + else + { + cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + } + + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); + const int circleMul = (hasRSC && !usingAxis ) ? 1 : 2; + + ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)(circleMul * halfCircleSegmentCount)); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + if (!gContext.mbUsing || usingAxis) + { + drawList->AddPolyline(circlePos, circleMul* halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + } + if(hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN)) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness); + } + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount + 1; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_FILL)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_BORDER), true, gContext.mStyle.RotationLineThickness); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + if (gContext.mStyle.HatchedAxisLineThickness <= 0.0f) + { + return; + } + + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, GetColorU32(HATCHED_AXIS_LINES), gContext.mStyle.HatchedAxisLineThickness); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + ImU32 scaleLineColor = GetColorU32(SCALE_LINE); + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness); + drawList->AddCircleFilled(worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor); + } + + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness); + } + drawList->AddCircleFilled(worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + + static void DrawScaleUniveralGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALEU)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALEU); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); + +#if 0 + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + /* + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + */ +#endif + drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); + } + } + } + + // draw screen cirle + drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawTranslationGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) + { + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + } + // draw plane + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) + { + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, GetColorU32(DIRECTION_X + i), true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) + { + ImU32 translationLineColor = GetColorU32(TRANSLATION_LINE); + + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static void HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + for (int j = 0; j < stepCount; j++) + { + float stepLength = 1.f / (float)stepCount; + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + ImU32 selectionColor = GetColorU32(SELECTION); + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z: %.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + dirAxis.TransformVector(gContext.mModelLocal); + dirPlaneX.TransformVector(gContext.mModelLocal); + dirPlaneY.TransformVector(gContext.mModelLocal); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + type = MT_SCALE_X + i; + } + } + + // universal + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) + { + type = MT_SCALE_XYZ; + } + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); + + float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos)); + if (distance < 12.f) + { + type = MT_SCALE_X + i; + } + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) + { + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + const ImGuiIO& io = ImGui::GetIO(); + const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + const float len = fabsf(signedLength); // near plan + const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + const float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(op, &gizmoHitProportion); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(op); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModelLocal.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModelLocal; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = GetRotateType(op); + + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetID(int id) + { + gContext.mActualID = id; + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + void SetAxisLimit(float value) + { + gContext.mAxisLimit=value; + } + + void SetPlaneLimit(float value) + { + gContext.mPlaneLimit = value; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f && !gContext.mbUsing) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + DrawScaleUniveralGizmo(operation, type); + } + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + cubeFace.color = directionColor | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + bool ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + return ViewManipulate(view, length, position, size, backgroundColor); + } + + void OrthoGraphic(const float l, float r, float b, const float t, float zn, const float zf, float* m16) + { + m16[0] = 2 / (r - l); + m16[1] = 0.0f; + m16[2] = 0.0f; + m16[3] = 0.0f; + m16[4] = 0.0f; + m16[5] = 2 / (t - b); + m16[6] = 0.0f; + m16[7] = 0.0f; + m16[8] = 0.0f; + m16[9] = 0.0f; + m16[10] = 1.0f / (zf - zn); + m16[11] = 0.0f; + m16[12] = (l + r) / (l - r); + m16[13] = (t + b) / (b - t); + m16[14] = zn / (zn - zf); + m16[15] = 1.0f; + } + + bool ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static bool isInside = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + OrthoGraphic(-1, 1, -1, 1, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), + ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), + ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), + ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), + ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + static int overBox = -1; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + IM_ASSERT(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; + + // draw face with lighter color + if (iPass) + { + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); + +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + if (io.MouseDown[0] && !isClicking && !isDraging && GImGui->ActiveId == 0) { + overBox = boxCoordInt; + isClicking = true; + isDraging = true; + } + } + } + } + } + } + + // draw axis + { + ImDrawList* drawList = gContext.mDrawList; + const vec_t origin = makeVect(-0.5f, -0.5f, -0.5f); + for (int i = 0; i < 3; ++i) { + vec_t dirAxis = directionUnary[i]; + ImVec2 baseSSpace = worldToPos(origin, res, position, size); + ImVec2 worldDirSSpace = worldToPos(origin + dirAxis, res, position, size); + + bool visible = false; + { + vec_t mid = origin + (dirAxis * 0.5); + vec_t eye = makeVect(0.f, 0.f, 0.5f); + eye.Normalize(); + for (int j = 1; j <= 2; j++) { + vec_t f = mid + (directionUnary[(i + j) % 3] * 0.5); + f.TransformVector(cubeView); + f.Normalize(); + auto w = f.Dot(eye); + if (w > 0) { + visible = true; + break; + } + } + } + + ImVec4 directionColorV = gContext.mStyle.Colors[DIRECTION_X + i]; + if (!visible) { + directionColorV.w *= 0.3f; + } + ImU32 directionColor = ImGui::ColorConvertFloat4ToU32(directionColorV); + drawList->AddLine(baseSSpace, worldDirSSpace, directionColor, gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + ImVec2 dir(baseSSpace - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, directionColor); + // Arrow head end + + // Axis text + ImVec2 labelSSpace = worldToPos(origin + dirAxis * 1.3f, res, position, size); + ImVec2 labelSize = ImGui::CalcTextSize(gContext.mStyle.AxisLabels[i]); + drawList->AddText(labelSSpace - labelSize * 0.5, directionColor, gContext.mStyle.AxisLabels[i]); + } + } + + bool viewUpdated = false; + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + viewUpdated = true; + } + isInside = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos); + + if (io.MouseDown[0] && (fabsf(io.MouseDelta[0]) || fabsf(io.MouseDelta[1])) && isClicking) + { + isClicking = false; + +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + if (!io.MouseDown[0]) + { + if (isClicking) + { + // apply new view direction + int cx = overBox / 9; + int cy = (overBox - cx * 9) / 3; + int cz = overBox % 3; + interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + + } + isClicking = false; + isDraging = false; + } + + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + viewUpdated = true; + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + + return viewUpdated; + } +}; diff --git a/src/imguizmo/ImGuizmo.h b/src/imguizmo/ImGuizmo.h new file mode 100644 index 00000000000..493cd7ce6dc --- /dev/null +++ b/src/imguizmo/ImGuizmo.h @@ -0,0 +1,282 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +#ifndef IMGUIZMO_NAMESPACE +#define IMGUIZMO_NAMESPACE ImGuizmo +#endif + +namespace IMGUIZMO_NAMESPACE +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + // Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). + IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + + // call BeginFrame right after ImGui_XXXX_NewFrame(); + IMGUI_API void BeginFrame(); + + // this is necessary because when imguizmo is compiled into a dll, and imgui into another + // globals are not shared between them. + // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam + // expose method to set imgui context + IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + IMGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + IMGUI_API bool IsUsing(); + + // return true if any gizmo is in moving state + IMGUI_API bool IsUsingAny(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + IMGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // ImGui::InputFloat3("Tr", matrixTranslation, 3); + // ImGui::InputFloat3("Rt", matrixRotation, 3); + // ImGui::InputFloat3("Sc", matrixScale, 3); + // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + // default is false + IMGUI_API void SetOrthographic(bool isOrthographic); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal + UNIVERSAL = TRANSLATE | ROTATE | SCALEU + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + // + // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en + // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as + // other software are using the same mechanics. But just in case, you are now warned! + // + IMGUI_API bool ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + // use this version if you did not call Manipulate before and you are just using ViewManipulate + IMGUI_API bool ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + IMGUI_API void SetID(int id); + + // return true if the cursor is over the operation's gizmo + IMGUI_API bool IsOver(OPERATION op); + IMGUI_API void SetGizmoSizeClipSpace(float value); + + // Allow axis to flip + // When true (default), the guizmo axis flip for better visibility + // When false, they always stay along the positive world/local axis + IMGUI_API void AllowAxisFlip(bool value); + + // Configure the limit where axis are hidden + IMGUI_API void SetAxisLimit(float value); + // Configure the limit where planes are hiden + IMGUI_API void SetPlaneLimit(float value); + + enum COLOR + { + DIRECTION_X, // directionColor[0] + DIRECTION_Y, // directionColor[1] + DIRECTION_Z, // directionColor[2] + PLANE_X, // planeColor[0] + PLANE_Y, // planeColor[1] + PLANE_Z, // planeColor[2] + SELECTION, // selectionColor + INACTIVE, // inactiveColor + TRANSLATION_LINE, // translationLineColor + SCALE_LINE, + ROTATION_USING_BORDER, + ROTATION_USING_FILL, + HATCHED_AXIS_LINES, + TEXT, + TEXT_SHADOW, + COUNT + }; + + enum Axis + { + Axis_X, + Axis_Y, + Axis_Z, + Axis_COUNT, + }; + + struct Style + { + IMGUI_API Style(); + + float TranslationLineThickness; // Thickness of lines for translation gizmo + float TranslationLineArrowSize; // Size of arrow at the end of lines for translation gizmo + float RotationLineThickness; // Thickness of lines for rotation gizmo + float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo + float ScaleLineThickness; // Thickness of lines for scale gizmo + float ScaleLineCircleSize; // Size of circle at the end of lines for scale gizmo + float HatchedAxisLineThickness; // Thickness of hatched axis lines + float CenterCircleSize; // Size of circle at the center of the translate/scale gizmo + + ImVec4 Colors[COLOR::COUNT]; + + char AxisLabels[Axis::Axis_COUNT][32]; + }; + + IMGUI_API Style& GetStyle(); +} diff --git a/src/imguizmo/LICENSE b/src/imguizmo/LICENSE new file mode 100644 index 00000000000..dcbee451c9c --- /dev/null +++ b/src/imguizmo/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cedric Guillemet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/imguizmo/README.md b/src/imguizmo/README.md new file mode 100644 index 00000000000..c4b081839bd --- /dev/null +++ b/src/imguizmo/README.md @@ -0,0 +1,192 @@ +# ImGuizmo + +Latest stable tagged version is 1.83. Current master version is 1.84 WIP. + +What started with the gizmo is now a collection of dear imgui widgets and more advanced controls. + +## Guizmos + +### ImViewGizmo + +Manipulate view orientation with 1 single line of code + +![Image of ImViewGizmo](http://i.imgur.com/7UVcyDd.gif) + +### ImGuizmo + +ImGizmo is a small (.h and .cpp) library built ontop of Dear ImGui that allow you to manipulate(Rotate & translate at the moment) 4x4 float matrices. No other dependancies. Coded with Immediate Mode (IM) philosophy in mind. + +Built against DearImgui 1.53WIP + +![Image of Rotation](http://i.imgur.com/y4mcVoT.gif) +![Image of Translation](http://i.imgur.com/o8q8iHq.gif) +![Image of Bounds](http://i.imgur.com/3Ez5LBr.gif) + +There is now a sample for Win32/OpenGL ! With a binary in bin directory. +![Image of Sample](https://i.imgur.com/nXlzyqD.png) + +### ImSequencer + +A WIP little sequencer used to edit frame start/end for different events in a timeline. +![Image of Rotation](http://i.imgur.com/BeyNwCn.png) +Check the sample for the documentation. More to come... + +### Graph Editor + +Nodes + connections. Custom draw inside nodes is possible with the delegate system in place. +![Image of GraphEditor](Images/nodeeditor.jpg) + +### API doc + +Call BeginFrame right after ImGui_XXXX_NewFrame(); + +```C++ +void BeginFrame(); +``` + +return true if mouse cursor is over any gizmo control (axis, plan or screen component) + +```C++ +bool IsOver();** +``` + +return true if mouse IsOver or if the gizmo is in moving state + +```C++ +bool IsUsing();** +``` + +enable/disable the gizmo. Stay in the state until next call to Enable. gizmo is rendered with gray half transparent color when disabled + +```C++ +void Enable(bool enable);** +``` + +helper functions for manualy editing translation/rotation/scale with an input float +translation, rotation and scale float points to 3 floats each +Angles are in degrees (more suitable for human editing) +example: + +```C++ + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); +``` + +These functions have some numerical stability issues for now. Use with caution. + +```C++ +void DecomposeMatrixToComponents(const float *matrix, float *translation, float *rotation, float *scale); +void RecomposeMatrixFromComponents(const float *translation, const float *rotation, const float *scale, float *matrix);** +``` + +Render a cube with face color corresponding to face normal. Usefull for debug/test + +```C++ +void DrawCube(const float *view, const float *projection, float *matrix);** +``` + +Call it when you want a gizmo +Needs view and projection matrices. +Matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional. snap points to a float[3] for translation and to a single float for scale or rotation. Snap angle is in Euler Degrees. + +```C++ + enum OPERATION + { + TRANSLATE, + ROTATE, + SCALE + }; + + enum MODE + { + LOCAL, + WORLD + }; + +void Manipulate(const float *view, const float *projection, OPERATION operation, MODE mode, float *matrix, float *deltaMatrix = 0, float *snap = 0);** +``` + +### ImGui Example + +Code for : + +![Image of dialog](http://i.imgur.com/GL5flN1.png) + +```C++ +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +``` + +## Install + +ImGuizmo can be installed via [vcpkg](https://github.com/microsoft/vcpkg) and used cmake + +```bash +vcpkg install imguizmo +``` + +See the [vcpkg example](/vcpkg-example) for more details + +## License + +ImGuizmo is licensed under the MIT License, see [LICENSE](/LICENSE) for more information. diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 2b805165d96..67fc3e81d9a 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -189,6 +189,9 @@ void AppConfig::set_defaults() if (get("show_gcode_window").empty()) set_bool("show_gcode_window", true); + if (get("show_3d_navigator").empty()) + set_bool("show_3d_navigator", true); + #ifdef _WIN32 diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index c6b689971d1..efa3936d89e 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -898,18 +898,16 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP else offset_top_surface = 0; // don't takes into account too thin areas - // skip if the exposed area is smaller than "min_width_top_surface" - double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2 + 10), scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))); - - Polygons grown_upper_slices = offset(*this->upper_slices, min_width_top_surface); - // get boungding box of last BoundingBox last_box = get_extents(orig_polygons); last_box.offset(SCALED_EPSILON); + // skip if the exposed area is smaller than "min_width_top_surface" + double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2. + 10), scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))); + // get the Polygons upper the polygon this layer - Polygons upper_polygons_series_clipped = - ClipperUtils::clip_clipper_polygons_with_subject_bbox(grown_upper_slices, last_box); + Polygons upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, last_box); + upper_polygons_series_clipped = offset(upper_polygons_series_clipped, min_width_top_surface); // set the clip to a virtual "second perimeter" fill_clip = offset_ex(orig_polygons, -double(ext_perimeter_spacing)); @@ -927,7 +925,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP const float bridge_margin = std::min(float(scale_(BRIDGE_INFILL_MARGIN)), float(scale_(nozzle_diameter * BRIDGE_INFILL_MARGIN / 0.4))); bridge_checker = offset_ex(diff_ex(orig_polygons, lower_polygons_series_clipped, ApplySafetyOffset::Yes), - 1.5 * bridge_offset + bridge_margin + perimeter_spacing / 2); + 1.5 * bridge_offset + bridge_margin + perimeter_spacing / 2.); } ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes); @@ -937,7 +935,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP ExPolygons temp_gap = diff_ex(top_polygons, fill_clip); ExPolygons inner_polygons = diff_ex(orig_polygons, - offset_ex(top_polygons, offset_top_surface + min_width_top_surface - double(ext_perimeter_spacing / 2)), + offset_ex(top_polygons, offset_top_surface + min_width_top_surface - double(ext_perimeter_spacing / 2.)), ApplySafetyOffset::Yes); // get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly // the polygons to fill. @@ -947,7 +945,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP //set the clip to the external wall but go back inside by infill_extrusion_width/2 to be sure the extrusion won't go outside even with a 100% overlap. double infill_spacing_unscaled = this->config->sparse_infill_line_width.get_abs_value(nozzle_diameter); if (infill_spacing_unscaled == 0) infill_spacing_unscaled = Flow::auto_extrusion_width(frInfill, nozzle_diameter); - fill_clip = offset_ex(orig_polygons, double(ext_perimeter_spacing / 2) - scale_(infill_spacing_unscaled / 2)); + fill_clip = offset_ex(orig_polygons, double(ext_perimeter_spacing / 2.) - scale_(infill_spacing_unscaled / 2.)); // ExPolygons oldLast = last; non_top_polygons = intersection_ex(inner_polygons, orig_polygons); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d08c9c3c343..351852df256 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -102,7 +102,8 @@ static t_config_enum_values s_keys_map_PrintHostType { { "flashair", htFlashAir }, { "astrobox", htAstroBox }, { "repetier", htRepetier }, - { "mks", htMKS } + { "mks", htMKS }, + { "obico", htObico } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) @@ -3050,6 +3051,7 @@ def = this->add("filament_loading_speed", coFloats); def->enum_values.push_back("astrobox"); def->enum_values.push_back("repetier"); def->enum_values.push_back("mks"); + def->enum_values.push_back("obico"); def->enum_labels.push_back("PrusaLink"); def->enum_labels.push_back("PrusaConnect"); def->enum_labels.push_back("Octo/Klipper"); @@ -3058,6 +3060,7 @@ def = this->add("filament_loading_speed", coFloats); def->enum_labels.push_back("AstroBox"); def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("MKS"); + def->enum_labels.push_back("Obico"); def->mode = comAdvanced; def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 73f126cd58d..07be77ab3d8 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -59,7 +59,7 @@ enum class FuzzySkinType { }; enum PrintHostType { - htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS + htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htObico }; enum AuthorizationType { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5386aad9336..0b1526f1ee6 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -524,6 +524,10 @@ set(SLIC3R_GUI_SOURCES GUI/calib_dlg.cpp Utils/CalibUtils.cpp Utils/CalibUtils.hpp + GUI/PrinterCloudAuthDialog.cpp + GUI/PrinterCloudAuthDialog.hpp + Utils/Obico.cpp + Utils/Obico.hpp ) if (WIN32) @@ -573,7 +577,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES}) encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto) +target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo GLEW::GLEW OpenGL::GL hidapi ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto) #target_link_libraries(libslic3r_gui libslic3r cereal imgui minilzo GLEW::GLEW OpenGL::GL hidapi libcurl OpenSSL::SSL OpenSSL::Crypto ${wxWidgets_LIBRARIES} glfw) if (MSVC) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index e8f21416afd..59051e2fdce 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -100,6 +100,7 @@ void CopyrightsDialog::fill_entries() { "GLFW", "", "https://www.glfw.org" }, { "GNU gettext", "", "https://www.gnu.org/software/gettext" }, { "ImGUI", "", "https://github.com/ocornut/imgui" }, + { "ImGuizmo", "", "https://github.com/CedricGuillemet/ImGuizmo" }, { "Libigl", "", "https://libigl.github.io" }, { "libnest2d", "", "https://github.com/tamasmeszaros/libnest2d" }, { "lib_fts", "", "https://www.forrestthewoods.com" }, diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 7206b5e6c4a..c6c04141948 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -419,6 +419,15 @@ void Camera::rotate_local_around_target(const Vec3d& rotation_rad) } } +void Camera::set_rotation(const Transform3d& rotation) +{ + const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target; + m_view_rotation = Eigen::Quaterniond(rotation.matrix().template block<3, 3>(0, 0)); + m_view_rotation.normalize(); + m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.)); + update_zenit(); +} + std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) { std::pair ret; diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index cfed47884cb..49a964b69eb 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -146,6 +146,8 @@ struct Camera // rotate the camera around three axes parallel to the camera local axes and passing through m_target void rotate_local_around_target(const Vec3d& rotation_rad); + void set_rotation(const Transform3d& rotation); + // returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; } diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 65cac2cc5e4..aa366e20eca 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -588,6 +588,14 @@ void TextCtrl::BUILD() { EnterPressed enter(this); propagate_value(); }), text_ctrl->GetId()); + } else { + // Orca: adds logic that scrolls the parent if the text control doesn't have focus + text_ctrl->Bind(wxEVT_MOUSEWHEEL, [text_ctrl](wxMouseEvent& event) { + if (text_ctrl->HasFocus() && text_ctrl->GetScrollRange(wxVERTICAL) != 1) + event.Skip(); // don't consume the event so that the text control will scroll + else + text_ctrl->GetParent()->ScrollLines((event.GetWheelRotation() > 0 ? -1 : 1) * event.GetLinesPerAction()); + }); } text_ctrl->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent &event) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e452aaabb34..227c93d15ad 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -85,6 +85,8 @@ #endif #include +#include + static constexpr const float TRACKBALLSIZE = 0.8f; static Slic3r::ColorRGBA DEFAULT_BG_LIGHT_COLOR = { 0.906f, 0.906f, 0.906f, 1.0f }; @@ -5535,6 +5537,70 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo return settings_changed; } +static float identityMatrix[16] = {1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; +static const float cameraProjection[16] = {1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; + +void GLCanvas3D::_render_3d_navigator() +{ + if (!wxGetApp().show_3d_navigator()) { + return; + } + + ImGuizmo::BeginFrame(); + ImGuizmo::AllowAxisFlip(false); + + auto& style = ImGuizmo::GetStyle(); + style.Colors[ImGuizmo::COLOR::DIRECTION_X] = ImGuiWrapper::to_ImVec4(ColorRGBA::Y()); + style.Colors[ImGuizmo::COLOR::DIRECTION_Y] = ImGuiWrapper::to_ImVec4(ColorRGBA::Z()); + style.Colors[ImGuizmo::COLOR::DIRECTION_Z] = ImGuiWrapper::to_ImVec4(ColorRGBA::X()); + strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_X], "y"); + strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "z"); + strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "x"); + + float sc = get_scale(); +#ifdef WIN32 + const int dpi = get_dpi_for_window(wxGetApp().GetTopWindow()); + sc *= (float) dpi / (float) DPI_DEFAULT; +#endif // WIN32 + + const ImGuiIO& io = ImGui::GetIO(); + const float viewManipulateLeft = 0; + const float viewManipulateTop = io.DisplaySize.y; + const float camDistance = 8.f; + ImGuizmo::SetID(0); + + Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = camera.get_view_rotation().toRotationMatrix(); + // Rotate along X and Z axis for 90 degrees to have Y-up + const auto coord_mapping_transform = Geometry::rotation_transform(Vec3d(0.5 * PI, 0, 0.5 * PI)); + m = m * coord_mapping_transform; + float cameraView[16]; + for (unsigned int c = 0; c < 4; ++c) { + for (unsigned int r = 0; r < 4; ++r) { + cameraView[c * 4 + r] = m(r, c); + } + } + + const float size = 128 * sc; + const bool dirty = ImGuizmo::ViewManipulate(cameraView, cameraProjection, ImGuizmo::OPERATION::ROTATE, ImGuizmo::MODE::WORLD, + identityMatrix, camDistance, ImVec2(viewManipulateLeft, viewManipulateTop - size), + ImVec2(size, size), 0x10101010); + + if (dirty) { + for (unsigned int c = 0; c < 4; ++c) { + for (unsigned int r = 0; r < 4; ++r) { + m(r, c) = cameraView[c * 4 + r]; + } + } + // Rotate back + m = m * (coord_mapping_transform.inverse()); + camera.set_rotation(m); + + request_extra_frame(); + } +} + #define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0 #if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT static void debug_output_thumbnail(const ThumbnailData& thumbnail_data) @@ -7324,6 +7390,8 @@ void GLCanvas3D::_render_overlays() }*/ } m_labels.render(sorted_instances); + + _render_3d_navigator(); } void GLCanvas3D::_render_style_editor() diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9d408a015f2..6f9de7c9ad2 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1181,6 +1181,7 @@ class GLCanvas3D //BBS: GUI refactor: adjust main toolbar position bool _render_orient_menu(float left, float right, float bottom, float top); bool _render_arrange_menu(float left, float right, float bottom, float top); + void _render_3d_navigator(); // render thumbnail using the default framebuffer void render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 893874ab390..f7606c8a075 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -329,6 +329,9 @@ class GUI_App : public wxApp bool show_gcode_window() const { return m_show_gcode_window; } void toggle_show_gcode_window(); + bool show_3d_navigator() const { return app_config->get_bool("show_3d_navigator"); } + void toggle_show_3d_navigator() const { app_config->set_bool("show_3d_navigator", !show_3d_navigator()); } + wxString get_inf_dialog_contect () {return m_info_dialog_content;}; std::vector split_str(std::string src, std::string separator); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 1dda42dbea2..984fa5e3fc4 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -2567,6 +2567,15 @@ void MainFrame::init_menubar_as_editor() this, [this]() { return m_tabpanel->GetSelection() == tpPreview; }, [this]() { return wxGetApp().show_gcode_window(); }, this); + append_menu_check_item( + viewMenu, wxID_ANY, _L("Show 3D Navigator"), _L("Show 3D navigator in Prepare and Preview scene"), + [this](wxCommandEvent&) { + wxGetApp().toggle_show_3d_navigator(); + m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT)); + }, + this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; }, + [this]() { return wxGetApp().show_3d_navigator(); }, this); + append_menu_item( viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"), [this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this, diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index bfd47e9835a..7b754644964 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -1,10 +1,12 @@ #include "PhysicalPrinterDialog.hpp" #include "PresetComboBoxes.hpp" +#include "PrinterCloudAuthDialog.hpp" #include #include #include #include +#include #include #include @@ -124,6 +126,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr this->update(); if (opt_key == "print_host") this->update_printhost_buttons(); + if (opt_key == "printhost_port") + this->update_ports(); }; m_optgroup->append_single_option_line("host_type"); @@ -161,12 +165,31 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr show_error(this, text); return; } + wxString msg; bool result; { // Show a wait cursor during the connection test, as it is blocking UI. wxBusyCursor wait; result = host->test(msg); + + if (!result && host->is_cloud()) { + PrinterCloudAuthDialog dlg(this->GetParent(), host.get()); + dlg.ShowModal(); + + auto api_key = dlg.GetApiKey(); + m_config->opt_string("printhost_apikey") = api_key; + result = !api_key.empty(); + if (result) { + if (Field* print_host_webui_field = this->m_optgroup->get_field("printhost_apikey"); print_host_webui_field) { + if (TextInput* temp_input = dynamic_cast(print_host_webui_field->getWindow()); temp_input) { + if (wxTextCtrl* temp = temp_input->GetTextCtrl()) { + temp->SetValue(wxString(api_key)); + } + } + } + } + } } if (result) show_info(this, host->get_test_ok_msg(), _L("Success!")); @@ -311,6 +334,42 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr update(); } +void PhysicalPrinterDialog::update_ports() { + const PrinterTechnology tech = Preset::printer_technology(*m_config); + if (tech == ptFFF) { + const auto opt = m_config->option>("host_type"); + if (opt->value == htObico) { + auto build_web_ui = [](DynamicPrintConfig* config) { + auto host = config->opt_string("print_host"); + auto port = config->opt_string("printhost_port"); + auto api_key = config->opt_string("printhost_apikey"); + if (host.empty() || port.empty()) { + return std::string(); + } + boost::regex re("\\[(\\d+)\\]"); + boost::smatch match; + if (!boost::regex_search(port, match, re)) + return std::string(); + if (match.size() <= 1) { + return std::string(); + } + boost::format urlFormat("%1%/printers/%2%/control"); + urlFormat % host % match[1]; + return urlFormat.str(); + }; + auto url = build_web_ui(m_config); + if (Field* print_host_webui_field = m_optgroup->get_field("print_host_webui"); print_host_webui_field) { + if (TextInput* temp_input = dynamic_cast(print_host_webui_field->getWindow()); temp_input) { + if (wxTextCtrl* temp = temp_input->GetTextCtrl()) { + temp->SetValue(wxString(url)); + m_config->opt_string("print_host_webui") = url; + } + } + } + } + } +} + void PhysicalPrinterDialog::update_printhost_buttons() { std::unique_ptr host(PrintHost::get_print_host(m_config)); @@ -415,6 +474,12 @@ void PhysicalPrinterDialog::update(bool printer_change) if (wxTextCtrl* temp = dynamic_cast(printhost_field->getWindow()); temp && temp->GetValue() == L"https://connect.prusa3d.com") { temp->SetValue(wxString()); } + + if (TextInput* temp_input = dynamic_cast(printhost_field->getWindow()); temp_input) { + if (wxTextCtrl* temp = temp_input->GetTextCtrl(); temp &&temp->GetValue() == L"https://app.obico.io") { + temp->SetValue(wxString()); + } + } } if (opt->value == htPrusaLink) { // PrusaConnect does NOT allow http digest m_optgroup->show_field("printhost_authorization_type"); @@ -436,6 +501,18 @@ void PhysicalPrinterDialog::update(bool printer_change) } } } + + if (opt->value == htObico) { + supports_multiple_printers = true; + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + if (TextInput* temp_input = dynamic_cast(printhost_field->getWindow()); temp_input) { + if (wxTextCtrl* temp = temp_input->GetTextCtrl(); temp && temp->GetValue().IsEmpty()) { + temp->SetValue(L"https://app.obico.io"); + m_config->opt_string("print_host") = "https://app.obico.io"; + } + } + } + } } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 93cd3f4aa90..9a38e7d8dbf 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -61,6 +61,7 @@ class PhysicalPrinterDialog : public DPIDialog void update_preset_input(); void update_printhost_buttons(); void update_printers(); + void update_ports(); protected: void on_dpi_changed(const wxRect& suggested_rect) override; diff --git a/src/slic3r/GUI/PrinterCloudAuthDialog.cpp b/src/slic3r/GUI/PrinterCloudAuthDialog.cpp new file mode 100644 index 00000000000..f46aa3c2bf6 --- /dev/null +++ b/src/slic3r/GUI/PrinterCloudAuthDialog.cpp @@ -0,0 +1,105 @@ +#include "PrinterCloudAuthDialog.hpp" +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include "MainFrame.hpp" +#include + +#include +#include +//------------------------------------------ +// PrinterCloundAuthDialog +//------------------------------------------ +namespace Slic3r { namespace GUI { + +PrinterCloudAuthDialog::PrinterCloudAuthDialog(wxWindow* parent, PrintHost* host) + : wxDialog((wxWindow*) (wxGetApp().mainframe), wxID_ANY, "Login"), m_host(host) +{ + SetBackgroundColour(*wxWHITE); + // Url + host->get_login_url(m_TargetUrl); + BOOST_LOG_TRIVIAL(info) << "login url = " << m_TargetUrl.ToStdString(); + + // Create the webview + m_browser = WebView::CreateWebView(this, m_TargetUrl); + if (m_browser == nullptr) { + wxLogError("Could not init m_browser"); + return; + } + m_browser->Hide(); + m_browser->SetSize(0, 0); + + // Connect the webview events + Bind(wxEVT_WEBVIEW_NAVIGATING, &PrinterCloudAuthDialog::OnNavigationRequest, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_NAVIGATED, &PrinterCloudAuthDialog::OnNavigationComplete, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_LOADED, &PrinterCloudAuthDialog::OnDocumentLoaded, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterCloudAuthDialog::OnNewWindow, this, m_browser->GetId()); + Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterCloudAuthDialog::OnScriptMessage, this, m_browser->GetId()); + + // UI + SetTitle(_L("Login")); + // Set a more sensible size for web browsing + wxSize pSize = FromDIP(wxSize(650, 840)); + SetSize(pSize); + + int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL); + int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL); + int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0; + wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY); + Move(tmpPT); +} + +PrinterCloudAuthDialog::~PrinterCloudAuthDialog() {} + +void PrinterCloudAuthDialog::OnNavigationRequest(wxWebViewEvent& evt) +{ + //todo +} + +void PrinterCloudAuthDialog::OnNavigationComplete(wxWebViewEvent& evt) +{ + m_browser->Show(); + Layout(); + //fortest + //WebView::RunScript(m_browser, "window.wx.postMessage('This is a web message')"); +} + +void PrinterCloudAuthDialog::OnDocumentLoaded(wxWebViewEvent& evt) +{ + // todo +} + +void PrinterCloudAuthDialog::OnNewWindow(wxWebViewEvent& evt) { + +} + +void PrinterCloudAuthDialog::OnScriptMessage(wxWebViewEvent& evt) +{ + wxString str_input = evt.GetString(); + try { + json j = json::parse(into_u8(str_input)); + wxString strCmd = j["command"]; + if (strCmd == "login_token") { + auto token = j["data"]["token"]; + m_host->set_api_key(token); + m_apikey = token; + } + Close(); + } catch (std::exception& e) { + wxMessageBox(e.what(), "parse json failed", wxICON_WARNING); + Close(); + } +} + +} +} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/PrinterCloudAuthDialog.hpp b/src/slic3r/GUI/PrinterCloudAuthDialog.hpp new file mode 100644 index 00000000000..9da01b32066 --- /dev/null +++ b/src/slic3r/GUI/PrinterCloudAuthDialog.hpp @@ -0,0 +1,51 @@ +#ifndef slic3r_GUI_PrinterCloudAuthDialog_hpp_ +#define slic3r_GUI_PrinterCloudAuthDialog_hpp_ + +#include +#include +#include +#include +#include +#include +#include "wx/webview.h" + +#if wxUSE_WEBVIEW_IE +#include "wx/msw/webview_ie.h" +#endif +#if wxUSE_WEBVIEW_EDGE +#include "wx/msw/webview_edge.h" +#endif + +#include "GUI_Utils.hpp" +#include "PrintHost.hpp" + +namespace Slic3r { namespace GUI { + +class PrinterCloudAuthDialog : public wxDialog +{ +protected: + wxWebView* m_browser; + wxString m_TargetUrl; + + wxString m_javascript; + wxString m_response_js; + PrintHost* m_host; + std::string m_apikey; + +public: + PrinterCloudAuthDialog(wxWindow* parent, PrintHost* host); + ~PrinterCloudAuthDialog(); + + std::string GetApiKey() { return m_apikey; }; + + void OnNavigationRequest(wxWebViewEvent& evt); + void OnNavigationComplete(wxWebViewEvent& evt); + void OnDocumentLoaded(wxWebViewEvent& evt); + void OnNewWindow(wxWebViewEvent& evt); + void OnScriptMessage(wxWebViewEvent& evt); + +}; + +}} // namespace Slic3r::GUI + +#endif \ No newline at end of file diff --git a/src/slic3r/Utils/Obico.cpp b/src/slic3r/Utils/Obico.cpp new file mode 100644 index 00000000000..edd439f3bb9 --- /dev/null +++ b/src/slic3r/Utils/Obico.cpp @@ -0,0 +1,218 @@ +#include "Obico.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/format.hpp" +#include "Http.hpp" +#include "libslic3r/AppConfig.hpp" +#include "Bonjour.hpp" +#include "slic3r/GUI/BonjourDialog.hpp" + +namespace fs = boost::filesystem; +namespace pt = boost::property_tree; + + +namespace Slic3r { + +Obico::Obico(DynamicPrintConfig* config) : + m_host(config->opt_string("print_host")), + m_web_ui(config->opt_string("print_host_webui")), + m_cafile(config->opt_string("printhost_cafile")), + m_port(config->opt_string("printhost_port")), + m_apikey(config->opt_string("printhost_apikey")), + m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke")) +{} + +const char* Obico::get_name() const { return "Obico"; } + +void Obico::set_api_key(const std::string auth_api_key) { m_apikey = auth_api_key; } + +std::string Obico::get_host() const { + return m_host; +} +void Obico::set_auth(Http& http) const +{ + http.header("Authorization", "Bearer " + m_apikey); + if (!m_cafile.empty()) { + http.ca_file(m_cafile); + } +} + +bool Obico::get_login_url(wxString& auth_url) const +{ + auth_url = make_url("o/authorize?response_type=token&client_id=OrcaSlicer&hide_navbar=true"); + return true; +} + +wxString Obico::get_test_ok_msg() const { return _(L("Connected to Obico successfully!")); } + +wxString Obico::get_test_failed_msg(wxString& msg) const +{ + return GUI::format_wxstr("%s: %s", _L("Could not connect to Obico"), msg.Truncate(256)); +} + +bool Obico::test(wxString& msg) const +{ + if (m_apikey.empty()) { + return false; + } + + bool res = true; + const char* name = get_name(); + auto url = make_url("api/v1/version/"); + + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; + // Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself. + auto http = Http::get(std::move(url)); + set_auth(http); + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % + body; + res = false; + msg = format_error(body, error, status); + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body; + }) +#ifdef WIN32 + .ssl_revoke_best_effort(m_ssl_revoke_best_effort) + .on_ip_resolve([&](std::string address) { + // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail. + // Remember resolved address to be reused at successive REST API call. + msg = GUI::from_u8(address); + }) +#endif // WIN32 + .perform_sync(); + + return res; +} + +bool Obico::get_printers(wxArrayString& printers) const +{ + const char* name = get_name(); + bool res = false; + auto url = make_url("api/v1/printers/"); + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: List printers at: %2%") % name % url; + + auto http = Http::get(std::move(url)); + set_auth(http); + + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error listing printers: %2%, HTTP %3%, body: `%4%`") % name % error % status % + body; + }) + .on_complete([&](std::string body, unsigned http_status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got printers: %2%, HTTP status: %3%") % name % body % http_status; + if (http_status != 200) + throw HostNetworkError(GUI::format(_L("HTTP status: %1%\nMessage body: \"%2%\""), http_status, body)); + + std::stringstream ss(body); + pt::ptree ptree; + try { + pt::read_json(ss, ptree); + } catch (const pt::ptree_error& err) { + throw HostNetworkError( + GUI::format(_L("Parsing of host response failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what())); + } + + const auto error = ptree.get_optional("error"); + if (error) + throw HostNetworkError(*error); + + try { + BOOST_FOREACH (boost::property_tree::ptree::value_type& v, ptree) { + const auto name = v.second.get("name"); + const auto port = v.second.get("id"); + printers.push_back(Slic3r::GUI::from_u8(name + " [" + port + "]")); + } + } catch (const std::exception& err) { + throw HostNetworkError( + GUI::format(_L("Enumeration of host printers failed.\nMessage body: \"%1%\"\nError: \"%2%\""), body, err.what())); + } + res = true; + }) + .perform_sync(); + + return res; +} + +PrintHostPostUploadActions Obico::get_post_upload_actions() const { + return PrintHostPostUploadAction::StartPrint; +} + +bool Obico::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const +{ + const char* name = get_name(); + const auto upload_filename = upload_data.upload_path.filename(); + const auto upload_parent_path = upload_data.upload_path.parent_path(); + wxString test_msg; + if (!test(test_msg)) { + error_fn(std::move(test_msg)); + return false; + } + + bool res = true; + auto url = make_url("api/v1/g_code_files/"); + + auto http = Http::post(url); // std::move(url)); + set_auth(http); + http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false") + .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? + .form_add("printer_id", m_port) + .form_add("filename", upload_filename.string()) + .form_add_file("file", upload_data.source_path.string(), upload_filename.string()) + + .on_complete([&](std::string body, unsigned status) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; + }) + .on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file to %2%: %3%, HTTP %4%, body: `%5%`") % name % url % error % + status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_progress([&](Http::Progress progress, bool& cancel) { + prorgess_fn(std::move(progress), cancel); + if (cancel) { + // Upload was canceled + BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled"; + res = false; + } + }) +#ifdef WIN32 + .ssl_revoke_best_effort(m_ssl_revoke_best_effort) +#endif + .perform_sync(); + return res; +} + +std::string Obico::make_url(const std::string& path) const +{ + if (m_host.find("http://") == 0 || m_host.find("https://") == 0) { + if (m_host.back() == '/') { + return (boost::format("%1%%2%") % m_host % path).str(); + } else { + return (boost::format("%1%/%2%") % m_host % path).str(); + } + } else { + return (boost::format("http://%1%/%2%") % m_host % path).str(); + } +} +} diff --git a/src/slic3r/Utils/Obico.hpp b/src/slic3r/Utils/Obico.hpp new file mode 100644 index 00000000000..c8c6e3a73d9 --- /dev/null +++ b/src/slic3r/Utils/Obico.hpp @@ -0,0 +1,51 @@ +#ifndef slic3r_Obico_hpp_ +#define slic3r_Obico_hpp_ + +#include +#include +#include +#include + +#include "PrintHost.hpp" +#include "libslic3r/PrintConfig.hpp" + +namespace Slic3r { + +class DynamicPrintConfig; +class Http; +class Obico : public PrintHost +{ +public: + Obico(DynamicPrintConfig* config); + ~Obico() override = default; + + const char* get_name() const override; + virtual bool can_test() const { return true; }; + bool has_auto_discovery() const override { return false; } + bool is_cloud() const override { return true; } + bool get_login_url(wxString& auth_url) const override; + void set_api_key(const std::string auth_api_key) override; + std::string get_host() const override; + + wxString get_test_ok_msg() const override; + wxString get_test_failed_msg(wxString& msg) const override; + virtual bool test(wxString& curl_msg) const override; + bool get_printers(wxArrayString& printers) const override; + PrintHostPostUploadActions get_post_upload_actions() const; + bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override; + +protected: + virtual void set_auth(Http& http) const; +private: + std::string m_host; + std::string m_port; + std::string m_apikey; + std::string m_cafile; + std::string m_web_ui; + bool m_ssl_revoke_best_effort; + + std::string make_url(const std::string& path) const; +}; +} // namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index c8f0e34bcaf..7b66f40800c 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -20,6 +20,7 @@ #include "Repetier.hpp" #include "MKS.hpp" #include "../GUI/PrintHostDialogs.hpp" +#include "Obico.hpp" namespace fs = boost::filesystem; using boost::optional; @@ -54,6 +55,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) case htPrusaLink: return new PrusaLink(config); case htPrusaConnect: return new PrusaConnect(config); case htMKS: return new MKS(config); + case htObico: return new Obico(config); default: return nullptr; } } else { diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index becaf138b49..ea5c8a3bafd 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -70,6 +70,11 @@ class PrintHost static PrintHost* get_print_host(DynamicPrintConfig *config); + //Support for cloud webui login + virtual bool is_cloud() const { return false; } + virtual bool get_login_url(wxString& auth_url) const { return false; } + virtual void set_api_key(const std::string auth_api_key) {} + protected: virtual wxString format_error(const std::string &body, const std::string &error, unsigned status) const; };