├── .gitignore ├── LICENSE ├── README.md ├── addons └── path-tool │ ├── dock │ ├── dock_theme.tres │ ├── path_tool_dock.gd │ └── path_tool_dock.tscn │ ├── exports │ └── .gdignore │ ├── path_node │ ├── hover_visualizer.tscn │ ├── materials │ │ └── interp_node_material.tres │ ├── path_point_3d.gd │ ├── path_point_manager.gd │ └── path_pointer │ │ ├── interp_mesh.tres │ │ └── path_pointer_3d.gd │ ├── plugin.cfg │ ├── plugin.gd │ └── save_resource.gd ├── project.godot └── test_scene.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot auto generated files 2 | *.gen.* 3 | .import/ 4 | .godot/ 5 | /gen/ 6 | 7 | # Misc 8 | logs/* 9 | *.log 10 | godot.osx.tools.* 11 | 12 | # Binaries 13 | *.o 14 | *.os 15 | *.so 16 | *.bc 17 | *.pyc 18 | *.dblite 19 | *.pdb 20 | *.lib 21 | bin 22 | *.config 23 | *.creator 24 | *.creator.user 25 | *.files 26 | *.includes 27 | *.idb 28 | 29 | # Gprof output 30 | gmon.out 31 | 32 | # Vim temp files 33 | *.swo 34 | *.swp 35 | 36 | # Qt project files 37 | *.config 38 | *.creator 39 | *.creator.* 40 | *.files 41 | *.includes 42 | *.cflags 43 | *.cxxflags 44 | 45 | # Eclipse CDT files 46 | .cproject 47 | .settings/ 48 | 49 | # Geany/geany-plugins files 50 | *.geany 51 | .geanyprj 52 | 53 | # Misc 54 | .DS_Store 55 | logs/ 56 | 57 | # for projects that use SCons for building: http://http://www.scons.org/ 58 | .sconf_temp 59 | .sconsign.dblite 60 | *.pyc 61 | 62 | # Visual C++ cache files 63 | ipch/ 64 | *.aps 65 | *.ncb 66 | *.opensdf 67 | *.sdf 68 | *.cachefile 69 | *.VC.db 70 | *.VC.opendb 71 | *.VC.VC.opendb 72 | enc_temp_folder/ 73 | 74 | # Visual Studio profiler 75 | *.psess 76 | *.vsp 77 | *.vspx 78 | 79 | # CodeLite project files 80 | *.project 81 | *.workspace 82 | .codelite/ 83 | 84 | # Windows Azure Build Output 85 | csx/ 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | sql/ 93 | *.Cache 94 | ClientBin/ 95 | [Ss]tyle[Cc]op.* 96 | ~$* 97 | *~ 98 | *.dbmdl 99 | *.dbproj.schemaview 100 | *.pfx 101 | *.publishsettings 102 | node_modules/ 103 | __pycache__/ 104 | 105 | # KDE 106 | .directory 107 | 108 | #Kdevelop project files 109 | *.kdev4 110 | 111 | # xCode 112 | xcuserdata 113 | 114 | # RIA/Silverlight projects 115 | Generated_Code/ 116 | 117 | # Backup & report files from converting an old project file to a newer 118 | # Visual Studio version. Backup files are not needed, because we have git ;-) 119 | _UpgradeReport_Files/ 120 | Backup*/ 121 | UpgradeLog*.XML 122 | UpgradeLog*.htm 123 | 124 | # SQL Server files 125 | App_Data/*.mdf 126 | App_Data/*.ldf 127 | 128 | # Business Intelligence projects 129 | *.rdl.data 130 | *.bim.layout 131 | *.bim_*.settings 132 | 133 | # Microsoft Fakes 134 | FakesAssemblies/ 135 | 136 | # ========================= 137 | # Windows detritus 138 | # ========================= 139 | 140 | # Windows image file caches 141 | Thumbs.db 142 | ehthumbs.db 143 | 144 | # Folder config file 145 | Desktop.ini 146 | 147 | # Recycle Bin used on file shares 148 | $RECYCLE.BIN/ 149 | logo.h 150 | *.autosave 151 | 152 | # https://github.com/github/gitignore/blob/master/Global/Tags.gitignore 153 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope 154 | TAGS 155 | !TAGS/ 156 | tags 157 | *.tags 158 | !tags/ 159 | gtags.files 160 | GTAGS 161 | GRTAGS 162 | GPATH 163 | cscope.files 164 | cscope.out 165 | cscope.in.out 166 | cscope.po.out 167 | godot.creator.* 168 | 169 | # Visual Studio 2017 and Visual Studio Code workspace folder 170 | /.vs 171 | /.vscode 172 | 173 | # Visual Studio Code workspace file 174 | *.code-workspace 175 | 176 | # Scons progress indicator 177 | .scons_node_count 178 | 179 | # ccls cache (https://github.com/MaskRay/ccls) 180 | .ccls-cache/ 181 | 182 | # compile commands (https://clang.llvm.org/docs/JSONCompilationDatabase.html) 183 | compile_commands.json 184 | builds 185 | build 186 | .idea 187 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Black Block Oy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Download the ZIP and extract it. 2 | Copy the `addons` directory to your godot 4.0 project root. 3 | Then in godot go to `project settings` -> `plugins` and enable the path-tool plugin. 4 | 5 | ![path_tool_screenshot3](https://user-images.githubusercontent.com/116638788/197763139-349a07a9-5654-41a3-8a6f-50b9402db08b.png) 6 | 7 | Demo video: [https://youtu.be/45_5DmTPMP0](https://youtu.be/45_5DmTPMP0) 8 | 9 | How to use: 10 | 11 | First before anything add a PathPointManager node to the 3D scene's root from "Add Child Node" button in the "Scene" dock. 12 | Add PathPoint3D (PP) nodes under the PathPointManager node from "Add Child Node" button in the "Scene" dock. 13 | Select PP you want to connect from and press "Assign source PathPoint3D (b)" from the PathToolDock. 14 | Then select PPs you want to connect to from the source PP and press "Assign next neighbours (n)" from the PathToolDock. 15 | Assigning a PP as a source node is only needed when assigning next neighbours directly as instructed above. 16 | 17 | OR the faster way: 18 | 19 | Click on a PP you want to connect from, then press "Create next neighbour (d)". 20 | It creates a new PP and automatically connects the old PP to the new one. 21 | Then just move the new new PP to a wanted position and repeat. 22 | 23 | OR the super fast way: 24 | 25 | Click on a PP you want to connect from, then press "Enable super mode (s)". 26 | Then move you mouse anywhere in the scene and press space bar to create a PP into the location of your mouse. 27 | It creates a new PP and connects the previously selected PP to the new one. 28 | You can keep placing new PPs like that as long as there's collision shapes where the mouse is. 29 | Alternatively, you don't need to have any PPs selected when starting to use super mode, 30 | using it this way just creates a new PP without any connections. 31 | When you're done, click "Disable super mode (s)". 32 | 33 | PP colors: 34 | 35 | Red = Not connected into or from anything 36 | 37 | Blue = Currently selected source PP 38 | 39 | Yellow = Connected from a PP but doesn't connect into any PPs 40 | 41 | Purple = Connects into PPs but is not connected from any PPs 42 | 43 | Green = Connected both ways 44 | 45 | 46 | You can manipulate path settings (curve value, auto curvature, weights for the csv export) by clicking on a path between two PPs. 47 | 48 | You can manipulate PP settings (tags) by clicking on a PP. 49 | 50 | ![path_tool_screenshot2](https://user-images.githubusercontent.com/116638788/197763762-842465c9-eaa6-4a0c-9665-ceeb188b15f2.png) 51 | -------------------------------------------------------------------------------- /addons/path-tool/dock/dock_theme.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Theme" load_steps=3 format=3 uid="uid://c84slgoylbqnd"] 2 | 3 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_aeq4u"] 4 | 5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kj2as"] 6 | bg_color = Color(0.145098, 0.168627, 0.203922, 1) 7 | border_width_left = 4 8 | border_width_bottom = 4 9 | border_color = Color(0.113725, 0.133333, 0.160784, 1) 10 | corner_radius_top_left = 2 11 | corner_radius_top_right = 2 12 | corner_radius_bottom_right = 2 13 | corner_radius_bottom_left = 2 14 | corner_detail = 4 15 | expand_margin_left = 4.0 16 | 17 | [resource] 18 | LineEdit/styles/focus = SubResource("StyleBoxEmpty_aeq4u") 19 | LineEdit/styles/normal = SubResource("StyleBoxFlat_kj2as") 20 | -------------------------------------------------------------------------------- /addons/path-tool/dock/path_tool_dock.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | @onready var AssignSourceButton:Button=$ScrollContainer/VBoxContainer/AssignSourceButton 5 | @onready var AssignNeighbourButton:Button=$ScrollContainer/VBoxContainer/AssignNeighbourButton 6 | @onready var UnassignSourceButton:Button=$ScrollContainer/VBoxContainer/UnassignSourceButton 7 | @onready var CreatePointButton:Button=$ScrollContainer/VBoxContainer/CreatePointButton 8 | @onready var DeleteEmptyPointsButton:Button=$ScrollContainer/VBoxContainer/DeleteEmptyPointsButton 9 | @onready var ClearNextNeighboursButton:Button=$ScrollContainer/VBoxContainer/ClearNextNeighboursButton 10 | @onready var SuperModeButton:Button=$ScrollContainer/VBoxContainer/ToggleSuperModeButton 11 | @onready var CreateNewIDButton:Button=$ScrollContainer/VBoxContainer/CreateNewIDButton 12 | @onready var StraightInterpSpinBox:SpinBox=$ScrollContainer/VBoxContainer/StraightInterpStep/VBoxContainer/StraightInterpSpinBox 13 | @onready var CurveInterpSpinBox:SpinBox=$ScrollContainer/VBoxContainer/CurveInterpStep/VBoxContainer/CurveInterpSpinBox 14 | @onready var StepSizeRoundingOptionButton:OptionButton=$ScrollContainer/VBoxContainer/StepSizeRoundingOptionButton 15 | 16 | var interp_debounce_timer := Timer.new() 17 | var update_thread := Thread.new() 18 | var check_empty_thread := Thread.new() 19 | var editor:EditorInterface 20 | var editor_camera:Camera3D 21 | var file_system_dock:FileSystemDock 22 | var full_selection:Array[Node] = [] 23 | var path_points_selection:Array[PathPoint3D] = [] 24 | 25 | var source_assigned := false 26 | var source_path_node:PathPoint3D 27 | var source_color:Color 28 | var super_mode_on := false 29 | 30 | var save_resource:SaveResource 31 | 32 | func _init(): 33 | set_process(false) 34 | if not Engine.is_editor_hint(): 35 | queue_free() 36 | 37 | 38 | func _ready(): 39 | if not is_in_group("path_tool_dock"): 40 | add_to_group("path_tool_dock") 41 | 42 | interp_debounce_timer.wait_time = 0.1 43 | interp_debounce_timer.one_shot = true 44 | interp_debounce_timer.timeout.connect(interp_debounce_timeout) 45 | add_child(interp_debounce_timer) 46 | 47 | var plugin:=EditorPlugin.new() 48 | editor=plugin.get_editor_interface() 49 | set_process(true) 50 | 51 | editor_camera=get_editor_cam() 52 | if not editor_camera: 53 | SuperModeButton.disabled = true 54 | SuperModeButton.text = "Enable super mode\n(editor 3D camera not found)" 55 | 56 | file_system_dock=editor.get_file_system_dock() 57 | 58 | load_data() 59 | 60 | 61 | func load_data() -> void: 62 | if ResourceLoader.exists(SaveResource.save_path): 63 | save_resource = ResourceLoader.load(SaveResource.save_path) 64 | else: 65 | save_resource = SaveResource.new(4, 1, 0) 66 | save_data() 67 | 68 | StraightInterpSpinBox.value = save_resource.straight_interpolation_step_size 69 | CurveInterpSpinBox.value = save_resource.curve_interpolation_step_size 70 | StepSizeRoundingOptionButton.select(save_resource.step_size_rounding) 71 | 72 | 73 | func save_data() -> void: 74 | ResourceSaver.save(save_resource, SaveResource.save_path) 75 | 76 | 77 | func get_editor_cam(): 78 | # very inconsistent but there's no built-in way to get the editor camera :´( 79 | var all_nodes = [] 80 | var cameras = [] 81 | var c = get_tree().get_root().get_children() 82 | while !c.is_empty(): 83 | var child_list = [] 84 | for i in c: 85 | child_list += i.get_children() 86 | all_nodes += child_list 87 | c = child_list 88 | 89 | for node in all_nodes: 90 | if node is Camera3D: 91 | cameras.append(node) 92 | 93 | return cameras[1] if cameras.size()>1 else null 94 | 95 | 96 | func _physics_process(_delta): 97 | var frame:=get_tree().get_frame() 98 | #if frame%2==0: 99 | full_selection=get_selection() 100 | path_points_selection=get_selected_path_points(full_selection) 101 | var path_points_selection_size:int=path_points_selection.size() 102 | super_mode_on=SuperModeButton.button_pressed 103 | 104 | if not super_mode_on: 105 | AssignSourceButton.disabled=path_points_selection_size!=1 106 | AssignNeighbourButton.disabled=!(source_assigned and path_points_selection_size>0 and not source_path_node in path_points_selection) 107 | CreatePointButton.disabled=AssignSourceButton.disabled 108 | ClearNextNeighboursButton.disabled=!path_points_selection_size 109 | CreateNewIDButton.disabled=!path_points_selection_size 110 | for path_point in path_points_selection: 111 | if not path_point.next_neighbours.is_empty() or not path_point.previous_neighbours.is_empty(): 112 | CreateNewIDButton.disabled=true 113 | break 114 | 115 | else: 116 | super_mode_loop() 117 | 118 | #if frame%6==0: 119 | if not check_empty_thread.is_alive() and not check_empty_thread.is_started(): 120 | check_empty_thread.start(thread_check_empty_path_points) 121 | 122 | if path_points_selection_size>=1 and not update_thread.is_alive() and not update_thread.is_started(): 123 | update_thread.start(thread_update_path_points.bind(path_points_selection)) 124 | 125 | for node in full_selection: 126 | if not node is PathPointer3D: 127 | return 128 | 129 | node.update_interpolation() 130 | 131 | 132 | func thread_update_path_points(nodes:Array[PathPoint3D]) -> void: 133 | for node in nodes: 134 | node.update_pointer_positions() 135 | node.update_prev_neighbour_pointers() 136 | node.update_color() 137 | 138 | call_deferred("update_thread_finish") 139 | 140 | 141 | func update_thread_finish() -> void: 142 | update_thread.wait_to_finish() 143 | 144 | 145 | func thread_check_empty_path_points() -> void: 146 | DeleteEmptyPointsButton.text = "Delete empty nodes" 147 | var empty_nodes:int = 0 148 | 149 | for node in get_tree().get_nodes_in_group("path_points"): 150 | if node.next_neighbours.is_empty() and node.previous_neighbours.is_empty(): 151 | empty_nodes += 1 152 | DeleteEmptyPointsButton.text = "Delete empty nodes: %s" % empty_nodes 153 | 154 | DeleteEmptyPointsButton.disabled = empty_nodes==0 155 | call_deferred("check_empty_thread_finish") 156 | 157 | 158 | func check_empty_thread_finish() -> void: 159 | check_empty_thread.wait_to_finish() 160 | 161 | 162 | func super_mode_loop() -> void: 163 | var mouse_pos=get_local_mouse_position()-Vector2(file_system_dock.size.x+28, 64) 164 | var ray_origin=editor_camera.project_ray_origin(mouse_pos) 165 | var ray_end=ray_origin+editor_camera.project_ray_normal(mouse_pos)*1000 166 | 167 | var query_params=PhysicsRayQueryParameters3D.new() 168 | query_params.from=ray_origin 169 | query_params.to=ray_end 170 | var space_state=editor_camera.get_world_3d().get_direct_space_state() 171 | var hit=space_state.intersect_ray(query_params) 172 | 173 | if hit: 174 | var hit_pos = hit.position 175 | hit_pos = Vector3( 176 | snapped(hit_pos.x, 0.2), 177 | snapped(hit_pos.y, 0.2), 178 | snapped(hit_pos.z, 0.2) 179 | ) 180 | get_tree().call_group("path_point_manager", "update_visualizer_position", hit_pos) 181 | 182 | if Input.is_action_just_pressed("place_super_node"): 183 | get_tree().call_group("path_point_manager", "create_path_point", hit_pos, null if path_points_selection.is_empty() else path_points_selection[0], editor) 184 | 185 | 186 | func get_selected_path_points(nodes) -> Array[PathPoint3D]: 187 | var path_point_nodes:Array[PathPoint3D] = [] 188 | 189 | for node in nodes: 190 | if node is PathPoint3D: 191 | path_point_nodes.append(node) 192 | 193 | return path_point_nodes 194 | 195 | 196 | func get_selection() -> Array: 197 | if is_instance_valid(editor): 198 | return editor.get_selection().get_selected_nodes() 199 | 200 | return [] 201 | 202 | 203 | func _on_assign_source_button_pressed() -> void: 204 | if source_path_node: 205 | _on_unassign_source_button_pressed() 206 | 207 | source_path_node = path_points_selection[0] 208 | source_path_node.mesh.material.albedo_color = Color.CYAN 209 | 210 | source_assigned = true 211 | editor.get_selection().clear() 212 | UnassignSourceButton.disabled = false 213 | 214 | 215 | func _on_assign_neighbour_button_pressed() -> void: 216 | source_path_node.clear_next_neighbours() 217 | 218 | for path_point in path_points_selection: 219 | path_point.add_previous_neighbour(source_path_node) 220 | source_path_node.add_next_neighbour(path_point) 221 | 222 | source_assigned=false 223 | 224 | if path_points_selection.size()!=1: 225 | editor.get_selection().clear() 226 | 227 | 228 | func _on_unassign_source_button_pressed() -> void: 229 | source_path_node.update_color() 230 | source_path_node=null 231 | source_assigned=false 232 | editor.get_selection().clear() 233 | UnassignSourceButton.disabled=true 234 | 235 | 236 | func _on_create_point_button_pressed() -> void: 237 | get_tree().call_group("path_point_manager", "create_path_point", path_points_selection[0].global_transform.origin, path_points_selection[0], editor) 238 | 239 | 240 | func _on_toggle_super_mode(pressed) -> void: 241 | if pressed: 242 | get_tree().call_group("path_point_manager", "show_visualizer") 243 | SuperModeButton.text="Disable super mode\n(the fastest way) (s)" 244 | 245 | AssignSourceButton.disabled=true 246 | AssignNeighbourButton.disabled=true 247 | UnassignSourceButton.disabled=true 248 | CreatePointButton.disabled=true 249 | ClearNextNeighboursButton.disabled=true 250 | CreateNewIDButton.disabled=true 251 | 252 | if source_path_node: 253 | source_path_node.update_color() 254 | source_path_node=null 255 | source_assigned=false 256 | 257 | else: 258 | get_tree().call_group("path_point_manager", "hide_visualizer") 259 | SuperModeButton.text="Enable super mode\n(the fastest way) (s)" 260 | 261 | 262 | func _on_delete_empty_points_button_pressed(): 263 | get_tree().call_group("path_points", "free_if_empty") 264 | get_node("DeleteEmptyNodesPopup").hide() 265 | 266 | 267 | func _on_clear_next_neighbours_button_pressed() -> void: 268 | for path_point in path_points_selection: 269 | path_point.clear_next_neighbours() 270 | 271 | 272 | func _on_create_new_id_button_pressed() -> void: 273 | for path_point in path_points_selection: 274 | if path_point.next_neighbours.is_empty() and path_point.previous_neighbours.is_empty(): 275 | path_point.assign_id() 276 | path_point.assign_mesh() 277 | 278 | 279 | func _on_export_csv_button_pressed() -> void: 280 | get_tree().call_group("path_point_manager", "export_paths_csv") 281 | 282 | 283 | func _on_invalid_export_path_chosen(path:String) -> void: 284 | get_node("InvalidExportPathPopup").popup_centered() 285 | get_node("InvalidExportPathPopup/VBox/PathLabel").text = path 286 | 287 | 288 | func _on_export_empty_nodes_found() -> void: 289 | get_node("DeleteEmptyNodesPopup").popup_centered() 290 | 291 | 292 | func _on_exporting_finished() -> void: 293 | get_node("ExportingFinishedPopup").popup_centered() 294 | 295 | 296 | func _on_path_point_visibility_button_toggled(button_pressed) -> void: 297 | get_tree().call_group("path_points", "set_path_points_visibility", button_pressed) 298 | 299 | 300 | func _on_pointer_visibility_button_toggled(button_pressed) -> void: 301 | get_tree().call_group("path_points", "set_pointers_visibility", button_pressed) 302 | 303 | 304 | func _on_interpolation_visibility_button_toggled(button_pressed) -> void: 305 | get_tree().call_group("path_points", "set_interpolations_visibility", button_pressed) 306 | 307 | 308 | func _on_help_button_pressed() -> void: 309 | $HelpPopup.popup_centered() 310 | 311 | 312 | func _on_straight_interp_step_size_changed(step_size:float): 313 | save_resource.straight_interpolation_step_size = step_size 314 | interp_debounce_timer.start() 315 | 316 | 317 | func _on_curve_interp_step_size_changed(step_size:float): 318 | save_resource.curve_interpolation_step_size = step_size 319 | interp_debounce_timer.start() 320 | 321 | 322 | func interp_debounce_timeout() -> void: 323 | save_data() 324 | update_path_pointer_data() 325 | 326 | 327 | func _on_step_size_rounding_option_selected(index): 328 | save_resource.step_size_rounding = index 329 | save_data() 330 | update_path_pointer_data() 331 | 332 | 333 | func request_step_size_data(path_pointer:PathPointer3D) -> void: 334 | path_pointer.update_step_size_data( 335 | save_resource.straight_interpolation_step_size, 336 | save_resource.curve_interpolation_step_size, 337 | save_resource.step_size_rounding 338 | ) 339 | 340 | 341 | func update_path_pointer_data() -> void: 342 | get_tree().call_group( 343 | "path_pointers", 344 | "update_step_size_data", 345 | save_resource.straight_interpolation_step_size, 346 | save_resource.curve_interpolation_step_size, 347 | save_resource.step_size_rounding 348 | ) 349 | -------------------------------------------------------------------------------- /addons/path-tool/dock/path_tool_dock.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=17 format=3 uid="uid://uckb2ya34iaf"] 2 | 3 | [ext_resource type="Theme" uid="uid://c84slgoylbqnd" path="res://addons/path-tool/dock/dock_theme.tres" id="1_iqexm"] 4 | [ext_resource type="Script" path="res://addons/path-tool/dock/path_tool_dock.gd" id="1_k6mf2"] 5 | 6 | [sub_resource type="InputEventKey" id="InputEventKey_1mhe5"] 7 | keycode = 66 8 | 9 | [sub_resource type="Shortcut" id="Shortcut_minys"] 10 | events = [SubResource("InputEventKey_1mhe5")] 11 | 12 | [sub_resource type="InputEventKey" id="InputEventKey_xfj43"] 13 | keycode = 78 14 | 15 | [sub_resource type="Shortcut" id="Shortcut_xgwbg"] 16 | events = [SubResource("InputEventKey_xfj43")] 17 | 18 | [sub_resource type="InputEventKey" id="InputEventKey_h826d"] 19 | keycode = 77 20 | 21 | [sub_resource type="Shortcut" id="Shortcut_otvw0"] 22 | events = [SubResource("InputEventKey_h826d")] 23 | 24 | [sub_resource type="InputEventKey" id="InputEventKey_yvkk1"] 25 | keycode = 68 26 | 27 | [sub_resource type="Shortcut" id="Shortcut_8vq0n"] 28 | events = [SubResource("InputEventKey_yvkk1")] 29 | 30 | [sub_resource type="InputEventKey" id="InputEventKey_unkfo"] 31 | keycode = 83 32 | 33 | [sub_resource type="Shortcut" id="Shortcut_0hxhv"] 34 | events = [SubResource("InputEventKey_unkfo")] 35 | 36 | [sub_resource type="InputEventKey" id="InputEventKey_ttvao"] 37 | keycode = 67 38 | 39 | [sub_resource type="Shortcut" id="Shortcut_3kcvb"] 40 | events = [SubResource("InputEventKey_ttvao")] 41 | 42 | [sub_resource type="InputEventKey" id="InputEventKey_88nul"] 43 | keycode = 88 44 | 45 | [sub_resource type="Shortcut" id="Shortcut_28hlk"] 46 | events = [SubResource("InputEventKey_88nul")] 47 | 48 | [node name="PathToolDock" type="Control"] 49 | layout_mode = 3 50 | anchors_preset = 15 51 | anchor_right = 1.0 52 | anchor_bottom = 1.0 53 | grow_horizontal = 2 54 | grow_vertical = 2 55 | theme = ExtResource("1_iqexm") 56 | script = ExtResource("1_k6mf2") 57 | 58 | [node name="ScrollContainer" type="ScrollContainer" parent="."] 59 | layout_mode = 1 60 | anchors_preset = 15 61 | anchor_right = 1.0 62 | anchor_bottom = 1.0 63 | grow_horizontal = 2 64 | grow_vertical = 2 65 | scroll_vertical = 100 66 | metadata/_edit_layout_mode = 1 67 | metadata/_edit_use_custom_anchors = false 68 | 69 | [node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"] 70 | layout_mode = 2 71 | offset_top = -100.0 72 | offset_right = 1144.0 73 | offset_bottom = 828.0 74 | size_flags_horizontal = 3 75 | size_flags_vertical = 3 76 | theme_override_constants/separation = 4 77 | 78 | [node name="AssignSourceButton" type="Button" parent="ScrollContainer/VBoxContainer"] 79 | layout_mode = 2 80 | offset_right = 1144.0 81 | offset_bottom = 54.0 82 | focus_mode = 0 83 | disabled = true 84 | shortcut = SubResource("Shortcut_minys") 85 | text = "Assign source 86 | PathPoint3D (b)" 87 | 88 | [node name="AssignNeighbourButton" type="Button" parent="ScrollContainer/VBoxContainer"] 89 | layout_mode = 2 90 | offset_top = 58.0 91 | offset_right = 1144.0 92 | offset_bottom = 112.0 93 | grow_horizontal = 2 94 | focus_mode = 0 95 | disabled = true 96 | shortcut = SubResource("Shortcut_xgwbg") 97 | text = "Assign next 98 | neighbours (n)" 99 | metadata/_edit_layout_mode = 1 100 | metadata/_edit_use_custom_anchors = false 101 | 102 | [node name="UnassignSourceButton" type="Button" parent="ScrollContainer/VBoxContainer"] 103 | layout_mode = 2 104 | offset_top = 116.0 105 | offset_right = 1144.0 106 | offset_bottom = 170.0 107 | focus_mode = 0 108 | disabled = true 109 | shortcut = SubResource("Shortcut_otvw0") 110 | text = "Unassign source 111 | PathPoint3D (m)" 112 | 113 | [node name="Filler1" type="Control" parent="ScrollContainer/VBoxContainer"] 114 | custom_minimum_size = Vector2i(0, 4) 115 | layout_mode = 2 116 | anchors_preset = 0 117 | offset_top = 174.0 118 | offset_right = 1144.0 119 | offset_bottom = 178.0 120 | 121 | [node name="CreatePointButton" type="Button" parent="ScrollContainer/VBoxContainer"] 122 | layout_mode = 2 123 | offset_top = 182.0 124 | offset_right = 1144.0 125 | offset_bottom = 236.0 126 | focus_mode = 0 127 | disabled = true 128 | shortcut = SubResource("Shortcut_8vq0n") 129 | text = "Create next neighbour 130 | (faster way) (d)" 131 | 132 | [node name="ToggleSuperModeButton" type="Button" parent="ScrollContainer/VBoxContainer"] 133 | layout_mode = 2 134 | offset_top = 240.0 135 | offset_right = 1144.0 136 | offset_bottom = 294.0 137 | focus_mode = 0 138 | toggle_mode = true 139 | shortcut = SubResource("Shortcut_0hxhv") 140 | text = "Enable super mode 141 | (the fastest way) (s)" 142 | 143 | [node name="Filler2" type="Control" parent="ScrollContainer/VBoxContainer"] 144 | custom_minimum_size = Vector2i(0, 4) 145 | layout_mode = 2 146 | anchors_preset = 0 147 | offset_top = 298.0 148 | offset_right = 1144.0 149 | offset_bottom = 302.0 150 | 151 | [node name="DeleteEmptyPointsButton" type="Button" parent="ScrollContainer/VBoxContainer"] 152 | layout_mode = 2 153 | offset_top = 306.0 154 | offset_right = 1144.0 155 | offset_bottom = 337.0 156 | focus_mode = 0 157 | disabled = true 158 | text = "Delete empty nodes" 159 | 160 | [node name="ClearNextNeighboursButton" type="Button" parent="ScrollContainer/VBoxContainer"] 161 | layout_mode = 2 162 | offset_top = 341.0 163 | offset_right = 1144.0 164 | offset_bottom = 395.0 165 | focus_mode = 0 166 | disabled = true 167 | shortcut = SubResource("Shortcut_3kcvb") 168 | text = "Clear next 169 | neighbour connections (c)" 170 | 171 | [node name="CreateNewIDButton" type="Button" parent="ScrollContainer/VBoxContainer"] 172 | layout_mode = 2 173 | offset_top = 399.0 174 | offset_right = 1144.0 175 | offset_bottom = 430.0 176 | focus_mode = 0 177 | disabled = true 178 | shortcut = SubResource("Shortcut_28hlk") 179 | text = "Make unique (x)" 180 | 181 | [node name="Filler3" type="Control" parent="ScrollContainer/VBoxContainer"] 182 | custom_minimum_size = Vector2i(0, 4) 183 | layout_mode = 2 184 | anchors_preset = 0 185 | offset_top = 434.0 186 | offset_right = 1144.0 187 | offset_bottom = 438.0 188 | 189 | [node name="PathPointVisibilityButton" type="Button" parent="ScrollContainer/VBoxContainer"] 190 | layout_mode = 2 191 | offset_top = 442.0 192 | offset_right = 1144.0 193 | offset_bottom = 473.0 194 | focus_mode = 0 195 | toggle_mode = true 196 | button_pressed = true 197 | text = "Toggle path points visibility" 198 | 199 | [node name="PointerVisibilityButton" type="Button" parent="ScrollContainer/VBoxContainer"] 200 | layout_mode = 2 201 | offset_top = 477.0 202 | offset_right = 1144.0 203 | offset_bottom = 508.0 204 | focus_mode = 0 205 | toggle_mode = true 206 | button_pressed = true 207 | text = "Toggle path pointers visibility" 208 | 209 | [node name="InterpolationVisibilityButton" type="Button" parent="ScrollContainer/VBoxContainer"] 210 | layout_mode = 2 211 | offset_top = 512.0 212 | offset_right = 1144.0 213 | offset_bottom = 543.0 214 | focus_mode = 0 215 | toggle_mode = true 216 | button_pressed = true 217 | text = "Toggle interpolations visibility" 218 | 219 | [node name="Filler4" type="Control" parent="ScrollContainer/VBoxContainer"] 220 | custom_minimum_size = Vector2i(0, 4) 221 | layout_mode = 2 222 | anchors_preset = 0 223 | offset_top = 547.0 224 | offset_right = 1144.0 225 | offset_bottom = 551.0 226 | 227 | [node name="StraightInterpStep" type="Panel" parent="ScrollContainer/VBoxContainer"] 228 | custom_minimum_size = Vector2i(0, 128) 229 | layout_mode = 2 230 | offset_top = 555.0 231 | offset_right = 1144.0 232 | offset_bottom = 683.0 233 | 234 | [node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer/VBoxContainer/StraightInterpStep"] 235 | layout_mode = 1 236 | anchors_preset = 15 237 | anchor_right = 1.0 238 | anchor_bottom = 1.0 239 | grow_horizontal = 2 240 | grow_vertical = 2 241 | alignment = 1 242 | 243 | [node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/StraightInterpStep/VBoxContainer"] 244 | layout_mode = 2 245 | offset_top = 35.0 246 | offset_right = 1144.0 247 | offset_bottom = 61.0 248 | text = "Straight interpolation step size:" 249 | horizontal_alignment = 1 250 | 251 | [node name="StraightInterpSpinBox" type="SpinBox" parent="ScrollContainer/VBoxContainer/StraightInterpStep/VBoxContainer"] 252 | layout_mode = 2 253 | offset_left = 532.0 254 | offset_top = 65.0 255 | offset_right = 611.062 256 | offset_bottom = 92.0 257 | size_flags_horizontal = 4 258 | min_value = 0.01 259 | step = 0.01 260 | value = 4.0 261 | custom_arrow_step = 0.1 262 | 263 | [node name="CurveInterpStep" type="Panel" parent="ScrollContainer/VBoxContainer"] 264 | custom_minimum_size = Vector2i(0, 128) 265 | layout_mode = 2 266 | offset_top = 687.0 267 | offset_right = 1144.0 268 | offset_bottom = 815.0 269 | 270 | [node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer/VBoxContainer/CurveInterpStep"] 271 | layout_mode = 1 272 | anchors_preset = 15 273 | anchor_right = 1.0 274 | anchor_bottom = 1.0 275 | grow_horizontal = 2 276 | grow_vertical = 2 277 | alignment = 1 278 | 279 | [node name="Label" type="Label" parent="ScrollContainer/VBoxContainer/CurveInterpStep/VBoxContainer"] 280 | layout_mode = 2 281 | offset_top = 35.0 282 | offset_right = 1144.0 283 | offset_bottom = 61.0 284 | text = "Curve interpolation step size:" 285 | horizontal_alignment = 1 286 | 287 | [node name="CurveInterpSpinBox" type="SpinBox" parent="ScrollContainer/VBoxContainer/CurveInterpStep/VBoxContainer"] 288 | layout_mode = 2 289 | offset_left = 532.0 290 | offset_top = 65.0 291 | offset_right = 611.062 292 | offset_bottom = 92.0 293 | size_flags_horizontal = 4 294 | min_value = 0.01 295 | step = 0.01 296 | value = 1.0 297 | custom_arrow_step = 0.1 298 | 299 | [node name="StepSizeRoundingOptionButton" type="OptionButton" parent="ScrollContainer/VBoxContainer"] 300 | layout_mode = 2 301 | offset_top = 819.0 302 | offset_right = 1144.0 303 | offset_bottom = 850.0 304 | alignment = 1 305 | item_count = 2 306 | selected = 1 307 | popup/item_0/text = "Step size maximized" 308 | popup/item_0/id = 0 309 | popup/item_1/text = "Step size minimized" 310 | popup/item_1/id = 1 311 | 312 | [node name="Filler5" type="Control" parent="ScrollContainer/VBoxContainer"] 313 | custom_minimum_size = Vector2i(0, 4) 314 | layout_mode = 2 315 | anchors_preset = 0 316 | offset_top = 854.0 317 | offset_right = 1144.0 318 | offset_bottom = 858.0 319 | 320 | [node name="ExportCSVButton" type="Button" parent="ScrollContainer/VBoxContainer"] 321 | layout_mode = 2 322 | offset_top = 862.0 323 | offset_right = 1144.0 324 | offset_bottom = 893.0 325 | focus_mode = 0 326 | text = "Export data as CSV" 327 | 328 | [node name="HelpButton" type="Button" parent="ScrollContainer/VBoxContainer"] 329 | layout_mode = 2 330 | offset_top = 897.0 331 | offset_right = 1144.0 332 | offset_bottom = 928.0 333 | focus_mode = 0 334 | text = "Help" 335 | 336 | [node name="ExportingFinishedPopup" type="PopupPanel" parent="."] 337 | size = Vector2i(200, 100) 338 | 339 | [node name="Label" type="Label" parent="ExportingFinishedPopup"] 340 | anchors_preset = 15 341 | anchor_right = 1.0 342 | anchor_bottom = 1.0 343 | offset_left = 4.0 344 | offset_top = 4.0 345 | offset_right = 196.0 346 | offset_bottom = 96.0 347 | grow_horizontal = 2 348 | grow_vertical = 2 349 | text = "CSV exporting finished." 350 | horizontal_alignment = 1 351 | metadata/_edit_use_custom_anchors = false 352 | 353 | [node name="HelpPopup" type="PopupPanel" parent="."] 354 | size = Vector2i(1000, 736) 355 | min_size = Vector2i(1000, 500) 356 | 357 | [node name="Label" type="Label" parent="HelpPopup"] 358 | offset_left = 4.0 359 | offset_top = 4.0 360 | offset_right = 996.0 361 | offset_bottom = 810.0 362 | grow_horizontal = 2 363 | grow_vertical = 2 364 | size_flags_horizontal = 3 365 | text = "Add a PathPointManager node to the 3D scene's root from \"Add Child Node\" button in the \"Scene\" dock. 366 | Add PathPoint3D (PP) nodes under the PathPointManager node from \"Add Child Node\" button in the \"Scene\" dock. 367 | Select PP you want to connect from and press \"Assign source PathPoint3D (b)\" from the PathToolDock. 368 | Then select PPs you want to connect to from the source PP and press \"Assign next neighbours (n)\" from the PathToolDock. 369 | Assigning a PP as a source node is only needed when assigning next neighbours directly as instructed above. 370 | 371 | OR the faster way: 372 | 373 | Click on a PP you want to connect from, then press \"Create next neighbour (d)\". 374 | It creates a new PP and automatically connects the old PP to the new one. 375 | Then just move the new new PP to a wanted position and repeat. 376 | 377 | OR the super fast way: 378 | 379 | Click on a PP you want to connect from, then press \"Enable super mode (s)\". 380 | Then move you mouse anywhere in the scene and press space bar to create a PP into the location of your mouse. 381 | It creates a new PP and connects the previously selected PP to the new one. 382 | You can keep placing new PPs like that as long as there's collision shapes where the mouse is. 383 | Alternatively, you don't need to have any PPs selected when starting to use super mode, 384 | using it this way just creates a new PP without any connections. 385 | When you're done, click \"Disable super mode (s)\". 386 | 387 | PP colors: 388 | Red = Not connected into or from anything 389 | Blue = Currently selected source PP 390 | Yellow = Connected from a PP but doesn't connect into any PPs 391 | Purple = Connects into PPs but is not connected from any PPs 392 | Green = Connected both ways 393 | 394 | You can manipulate path settings by clicking on a path between two PPs. 395 | You can manipulate PP settings by clicking on a PP. 396 | " 397 | metadata/_edit_use_custom_anchors = false 398 | 399 | [node name="DeleteEmptyNodesPopup" type="PopupPanel" parent="."] 400 | size = Vector2i(400, 200) 401 | 402 | [node name="VBoxContainer" type="VBoxContainer" parent="DeleteEmptyNodesPopup"] 403 | anchors_preset = 15 404 | anchor_right = 1.0 405 | anchor_bottom = 1.0 406 | offset_left = 4.0 407 | offset_top = 4.0 408 | offset_right = 396.0 409 | offset_bottom = 196.0 410 | grow_horizontal = 2 411 | grow_vertical = 2 412 | size_flags_horizontal = 3 413 | size_flags_vertical = 3 414 | alignment = 1 415 | metadata/_edit_use_custom_anchors = false 416 | 417 | [node name="Label" type="Label" parent="DeleteEmptyNodesPopup/VBoxContainer"] 418 | layout_mode = 2 419 | offset_top = 52.0 420 | offset_right = 392.0 421 | offset_bottom = 104.0 422 | text = "Path points found with no connections. 423 | Exporting aborted." 424 | horizontal_alignment = 1 425 | 426 | [node name="DeleteEmptyNodesButton" type="Button" parent="DeleteEmptyNodesPopup/VBoxContainer"] 427 | layout_mode = 2 428 | offset_top = 108.0 429 | offset_right = 392.0 430 | offset_bottom = 139.0 431 | focus_mode = 0 432 | text = "Delete empty path points" 433 | 434 | [node name="InvalidExportPathPopup" type="PopupPanel" parent="."] 435 | size = Vector2i(193, 100) 436 | 437 | [node name="VBox" type="VBoxContainer" parent="InvalidExportPathPopup"] 438 | offset_left = 4.0 439 | offset_top = 4.0 440 | offset_right = 189.0 441 | offset_bottom = 96.0 442 | 443 | [node name="Label" type="Label" parent="InvalidExportPathPopup/VBox"] 444 | layout_mode = 2 445 | offset_right = 185.0 446 | offset_bottom = 26.0 447 | text = "Invalid CSV export path:" 448 | horizontal_alignment = 1 449 | 450 | [node name="PathLabel" type="Label" parent="InvalidExportPathPopup/VBox"] 451 | layout_mode = 2 452 | offset_top = 30.0 453 | offset_right = 185.0 454 | offset_bottom = 53.0 455 | horizontal_alignment = 1 456 | 457 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/AssignSourceButton" to="." method="_on_assign_source_button_pressed"] 458 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/AssignNeighbourButton" to="." method="_on_assign_neighbour_button_pressed"] 459 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/UnassignSourceButton" to="." method="_on_unassign_source_button_pressed"] 460 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/CreatePointButton" to="." method="_on_create_point_button_pressed"] 461 | [connection signal="toggled" from="ScrollContainer/VBoxContainer/ToggleSuperModeButton" to="." method="_on_toggle_super_mode"] 462 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/DeleteEmptyPointsButton" to="." method="_on_delete_empty_points_button_pressed"] 463 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/ClearNextNeighboursButton" to="." method="_on_clear_next_neighbours_button_pressed"] 464 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/CreateNewIDButton" to="." method="_on_create_new_id_button_pressed"] 465 | [connection signal="toggled" from="ScrollContainer/VBoxContainer/PathPointVisibilityButton" to="." method="_on_path_point_visibility_button_toggled"] 466 | [connection signal="toggled" from="ScrollContainer/VBoxContainer/PointerVisibilityButton" to="." method="_on_pointer_visibility_button_toggled"] 467 | [connection signal="toggled" from="ScrollContainer/VBoxContainer/InterpolationVisibilityButton" to="." method="_on_interpolation_visibility_button_toggled"] 468 | [connection signal="value_changed" from="ScrollContainer/VBoxContainer/StraightInterpStep/VBoxContainer/StraightInterpSpinBox" to="." method="_on_straight_interp_step_size_changed"] 469 | [connection signal="value_changed" from="ScrollContainer/VBoxContainer/CurveInterpStep/VBoxContainer/CurveInterpSpinBox" to="." method="_on_curve_interp_step_size_changed"] 470 | [connection signal="item_selected" from="ScrollContainer/VBoxContainer/StepSizeRoundingOptionButton" to="." method="_on_step_size_rounding_option_selected"] 471 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/ExportCSVButton" to="." method="_on_export_csv_button_pressed"] 472 | [connection signal="pressed" from="ScrollContainer/VBoxContainer/HelpButton" to="." method="_on_help_button_pressed"] 473 | [connection signal="pressed" from="DeleteEmptyNodesPopup/VBoxContainer/DeleteEmptyNodesButton" to="." method="_on_delete_empty_points_button_pressed"] 474 | -------------------------------------------------------------------------------- /addons/path-tool/exports/.gdignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackblock-io/path-tool/dfaae7efdfa14bbc251e2441508abe0d734f4bd5/addons/path-tool/exports/.gdignore -------------------------------------------------------------------------------- /addons/path-tool/path_node/hover_visualizer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://cv4f3pf3pycyd"] 2 | 3 | [ext_resource type="Material" uid="uid://dmrb68hyxqcdw" path="res://addons/path-tool/path_node/materials/interp_node_material.tres" id="1_j6chf"] 4 | 5 | [sub_resource type="CylinderMesh" id="CylinderMesh_jrcux"] 6 | material = ExtResource("1_j6chf") 7 | top_radius = 0.0 8 | bottom_radius = 0.3 9 | height = 1.0 10 | radial_segments = 4 11 | rings = 1 12 | 13 | [sub_resource type="PlaneMesh" id="PlaneMesh_x6m51"] 14 | material = ExtResource("1_j6chf") 15 | size = Vector2(0.2, 10) 16 | 17 | [node name="HoverVisualizer" type="Node3D"] 18 | 19 | [node name="Cone" type="MeshInstance3D" parent="."] 20 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0) 21 | cast_shadow = 0 22 | mesh = SubResource("CylinderMesh_jrcux") 23 | skeleton = NodePath("../..") 24 | 25 | [node name="Bar1" type="MeshInstance3D" parent="."] 26 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.01, 0) 27 | cast_shadow = 0 28 | mesh = SubResource("PlaneMesh_x6m51") 29 | 30 | [node name="Bar2" type="MeshInstance3D" parent="."] 31 | transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 0.01, 0) 32 | cast_shadow = 0 33 | mesh = SubResource("PlaneMesh_x6m51") 34 | -------------------------------------------------------------------------------- /addons/path-tool/path_node/materials/interp_node_material.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StandardMaterial3D" format=3 uid="uid://dmrb68hyxqcdw"] 2 | 3 | [resource] 4 | shading_mode = 0 5 | disable_ambient_light = true 6 | albedo_color = Color(0.360784, 1, 0.937255, 1) 7 | -------------------------------------------------------------------------------- /addons/path-tool/path_node/path_point_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends MeshInstance3D 3 | class_name PathPoint3D 4 | 5 | const colors:Array[Color] = [ 6 | Color.DODGER_BLUE, 7 | Color.MEDIUM_PURPLE, 8 | Color.GOLD, 9 | Color.DEEP_PINK, 10 | Color.FIREBRICK, 11 | Color.CYAN, 12 | Color.PLUM, 13 | Color.ORANGE_RED, 14 | Color.SEASHELL, 15 | Color.YELLOW_GREEN, 16 | Color.SADDLE_BROWN, 17 | Color.ROSY_BROWN, 18 | Color.PALE_VIOLET_RED, 19 | Color.OLIVE, 20 | Color.LIME_GREEN, 21 | Color.DARK_SLATE_BLUE 22 | ] 23 | 24 | @export_range(0, 64, 1) var tag:int = 0 25 | @export var next_neighbours:Array[String] = [] 26 | @export var previous_neighbours:Array[String] = [] 27 | 28 | 29 | func _ready(): 30 | update_pointer_positions() 31 | 32 | if not mesh: 33 | assign_id() 34 | assign_mesh() 35 | 36 | if not is_in_group("path_points"): 37 | add_to_group("path_points") 38 | 39 | 40 | func assign_id(): 41 | set_name(str(generate_new_id())) 42 | 43 | 44 | func generate_new_id() -> int: 45 | randomize() 46 | var day=str(Time.get_datetime_dict_from_system()["day"]) 47 | var micros=str(Time.get_ticks_usec()) 48 | var r=str(randi()%1000) 49 | return (day+micros+r).to_int() 50 | 51 | 52 | func assign_mesh(): 53 | mesh = SphereMesh.new() 54 | mesh.is_hemisphere = true 55 | mesh.height = 0.7 56 | mesh.radius = 0.7 57 | mesh.radial_segments = 6 58 | mesh.rings = 3 59 | mesh.material = StandardMaterial3D.new() 60 | mesh.material.set_shading_mode(BaseMaterial3D.SHADING_MODE_UNSHADED) 61 | set_cast_shadows_setting(GeometryInstance3D.SHADOW_CASTING_SETTING_OFF) 62 | transparency = 0.2 63 | 64 | update_color() 65 | 66 | 67 | func update_color() -> void: 68 | var has_next_neighbours:bool=!next_neighbours.is_empty() 69 | var has_prev_neighbours:bool=!previous_neighbours.is_empty() 70 | 71 | if has_next_neighbours and has_prev_neighbours: 72 | mesh.material.albedo_color = Color.GREEN 73 | 74 | elif not has_next_neighbours and has_prev_neighbours: 75 | mesh.material.albedo_color = Color.YELLOW 76 | 77 | elif has_next_neighbours and not has_prev_neighbours: 78 | mesh.material.albedo_color = Color.PURPLE 79 | 80 | else: 81 | mesh.material.albedo_color = Color.RED 82 | 83 | 84 | func update_pointers() -> void: 85 | for pointer in get_children(): 86 | pointer.queue_free() 87 | 88 | if next_neighbours.is_empty(): 89 | return 90 | 91 | for next_n_name in next_neighbours: 92 | var next_neighbour=get_next_neighbour(next_n_name) 93 | if not next_neighbour: 94 | continue 95 | 96 | var pointer := PathPointer3D.new() 97 | add_child(pointer, true) 98 | pointer.init(str(next_neighbour.name), colors[tag % colors.size()]) 99 | 100 | update_pointer_positions() 101 | 102 | 103 | func update_pointer_positions() -> void: 104 | for child in get_children(): 105 | if child is PathPointer3D: 106 | var next_n_name:String=child.target_name 107 | var next_neighbour=get_next_neighbour(next_n_name) 108 | if not next_neighbour: 109 | child.queue_free() 110 | continue 111 | 112 | var own_pos:Vector3=global_transform.origin 113 | var target_pos:Vector3=next_neighbour.global_transform.origin 114 | 115 | child.update_position(self, next_neighbour) 116 | 117 | 118 | func update_prev_neighbour_pointers() -> void: 119 | for prev_n_name in previous_neighbours: 120 | var prev_neighbour=get_prev_neighbour(prev_n_name) 121 | if not prev_neighbour: 122 | update_color() 123 | update_pointers() 124 | continue 125 | 126 | prev_neighbour.update_pointer_positions() 127 | 128 | 129 | func add_previous_neighbour(prev_neighbour:PathPoint3D) -> void: 130 | var prev_n_name:String = str(prev_neighbour.name) 131 | if not prev_n_name in previous_neighbours: 132 | previous_neighbours.append(prev_n_name) 133 | 134 | 135 | func add_next_neighbour(next_neighbour:PathPoint3D) -> void: 136 | var next_n_name:String = str(next_neighbour.name) 137 | if not next_n_name in next_neighbours: 138 | next_neighbours.append(next_n_name) 139 | next_neighbour.update_color() 140 | 141 | update_color() 142 | update_pointers() 143 | 144 | 145 | func clear_next_neighbours() -> void: 146 | for next_n_name in next_neighbours: 147 | var next_neighbour=get_next_neighbour(next_n_name) 148 | if not next_neighbour: 149 | continue 150 | 151 | next_neighbour.previous_neighbours.erase(str(name)) 152 | next_neighbour.update_color() 153 | 154 | next_neighbours.clear() 155 | update_color() 156 | update_pointers() 157 | 158 | 159 | func get_next_neighbour(_name:String) -> Node: 160 | if not _name in next_neighbours: 161 | return null 162 | 163 | if not get_parent().has_node(_name): 164 | next_neighbours.erase(_name) 165 | return null 166 | 167 | return get_parent().get_node(_name) 168 | 169 | 170 | func get_prev_neighbour(_name:String) -> Node: 171 | if not _name in previous_neighbours: 172 | return null 173 | 174 | if not get_parent().has_node(_name): 175 | previous_neighbours.erase(_name) 176 | return null 177 | 178 | return get_parent().get_node(_name) 179 | 180 | 181 | func set_path_points_visibility(pressed:bool) -> void: 182 | if pressed: 183 | visibility_range_end=0 184 | else: 185 | visibility_range_end=0.1 186 | 187 | 188 | func set_pointers_visibility(pressed:bool) -> void: 189 | for child in get_children(): 190 | if child is PathPointer3D: 191 | if pressed: 192 | child.visibility_range_end=0 193 | else: 194 | child.visibility_range_end=0.1 195 | 196 | 197 | func set_interpolations_visibility(pressed:bool) -> void: 198 | for child in get_children(): 199 | if child is PathPointer3D: 200 | if pressed: 201 | child.get_child(0, true).show() 202 | else: 203 | child.get_child(0, true).hide() 204 | 205 | 206 | func free_if_empty() -> void: 207 | if next_neighbours.is_empty() and previous_neighbours.is_empty(): 208 | queue_free() 209 | -------------------------------------------------------------------------------- /addons/path-tool/path_node/path_point_manager.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node3D 3 | class_name PathPointManager 4 | 5 | @export_global_dir var csv_export_path = "res://addons/path-tool/exports" 6 | @export var csv_export_file_name:String = "roadnet" 7 | 8 | @export var show_paths_run_time:bool = false 9 | 10 | var visualizer:Node3D = null 11 | 12 | func _enter_tree(): 13 | if not Engine.is_editor_hint(): 14 | if not show_paths_run_time: 15 | queue_free() 16 | 17 | else: 18 | set_process(false) 19 | 20 | 21 | func _ready(): 22 | if not is_in_group("path_point_manager"): 23 | add_to_group("path_point_manager") 24 | 25 | if not visualizer: 26 | visualizer = load("res://addons/path-tool/path_node/hover_visualizer.tscn").instantiate() 27 | visualizer.hide() 28 | add_child(visualizer) 29 | 30 | 31 | func create_path_point(pos:Vector3, connect_from, editor:EditorInterface) -> void: 32 | var new_path_point := PathPoint3D.new() 33 | add_child(new_path_point) 34 | new_path_point.set_owner(get_tree().get_edited_scene_root()) 35 | 36 | new_path_point.update_color() 37 | new_path_point.global_transform.origin = pos 38 | 39 | if connect_from: 40 | var old_path_point:PathPoint3D = connect_from 41 | new_path_point.add_previous_neighbour(old_path_point) 42 | old_path_point.add_next_neighbour(new_path_point) 43 | new_path_point.tag = old_path_point.tag 44 | 45 | editor.get_selection().clear() 46 | editor.get_selection().add_node(new_path_point) 47 | 48 | 49 | func hide_visualizer() -> void: 50 | visualizer.hide() 51 | 52 | 53 | func show_visualizer() -> void: 54 | visualizer.show() 55 | 56 | 57 | func update_visualizer_position(pos:Vector3) -> void: 58 | visualizer.global_transform.origin=pos 59 | 60 | 61 | func export_paths_csv() -> void: 62 | print("Starting CSV exporting") 63 | var path:String = csv_export_path + "/" + csv_export_file_name + ".csv" 64 | var path_points:Array = get_tree().get_nodes_in_group("path_points") 65 | var file := FileAccess.open(path, FileAccess.WRITE) 66 | if not file: 67 | get_tree().call_group("path_tool_dock", "_on_invalid_export_path_chosen", path) 68 | return 69 | 70 | file.store_csv_line(PackedStringArray(["id", "tag", "position", "neighbours"]), "\t") 71 | 72 | for path_point in path_points: 73 | if path_point.next_neighbours.is_empty() and path_point.previous_neighbours.is_empty(): 74 | get_tree().call_group("path_tool_dock", "_on_export_empty_nodes_found") 75 | return 76 | 77 | var line:PackedStringArray 78 | var point_pos:Vector3 = path_point.global_transform.origin 79 | point_pos=Vector3( 80 | snapped(point_pos.x, 0.01), 81 | snapped(point_pos.y, 0.01), 82 | snapped(point_pos.z, 0.01) 83 | ) 84 | line=[path_point.name, path_point.tag, str(point_pos)] 85 | 86 | var neighbours := [] 87 | for pointer in path_point.get_children(): 88 | if not pointer is PathPointer3D: 89 | continue 90 | 91 | var interp_nodes:Array[Vector3] = [] 92 | for interp_pos in pointer.interpolation_positions: 93 | interp_nodes.append(Vector3( 94 | snapped(interp_pos.x, 0.01), 95 | snapped(interp_pos.y, 0.01), 96 | snapped(interp_pos.z, 0.01) 97 | )) 98 | 99 | neighbours.append([pointer.target_name.to_int(), pointer.weight, interp_nodes]) 100 | line.append_array([neighbours]) 101 | file.store_csv_line(line, "\t") 102 | 103 | file = null 104 | print("Finished CSV exporting to path: %s" % path) 105 | get_tree().call_group("path_tool_dock", "_on_exporting_finished") 106 | -------------------------------------------------------------------------------- /addons/path-tool/path_node/path_pointer/interp_mesh.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="ArrayMesh" load_steps=3 format=3 uid="uid://bx1rr8vxbsmow"] 2 | 3 | [ext_resource type="Material" uid="uid://dmrb68hyxqcdw" path="res://addons/path-tool/path_node/materials/interp_node_material.tres" id="1_cu231"] 4 | 5 | [sub_resource type="ArrayMesh" id="ArrayMesh_4v5ep"] 6 | _surfaces = [{ 7 | "aabb": AABB(-0.199715, -0.0250091, -0.47915, 0.39943, 0.0500183, 0.799851), 8 | "format": 4097, 9 | "index_count": 24, 10 | "index_data": PackedByteArray(0, 0, 3, 0, 1, 0, 0, 0, 2, 0, 3, 0, 1, 0, 3, 0, 5, 0, 4, 0, 1, 0, 5, 0, 4, 0, 0, 0, 1, 0, 0, 0, 4, 0, 2, 0, 2, 0, 5, 0, 3, 0, 2, 0, 4, 0, 5, 0), 11 | "primitive": 3, 12 | "vertex_count": 6, 13 | "vertex_data": PackedByteArray(228, 15, 181, 178, 249, 223, 204, 188, 39, 83, 245, 190, 228, 15, 181, 178, 249, 223, 204, 60, 39, 83, 245, 190, 11, 130, 76, 62, 249, 223, 204, 188, 248, 50, 164, 62, 11, 130, 76, 62, 249, 223, 204, 60, 248, 50, 164, 62, 17, 130, 76, 190, 249, 223, 204, 188, 245, 50, 164, 62, 17, 130, 76, 190, 249, 223, 204, 60, 245, 50, 164, 62) 14 | }] 15 | blend_shape_mode = 0 16 | 17 | [resource] 18 | resource_name = "interp_mesh_Cylinder" 19 | _surfaces = [{ 20 | "aabb": AABB(-0.199715, -0.0250091, -0.47915, 0.39943, 0.0500183, 0.799851), 21 | "attribute_data": PackedByteArray(0, 0, 128, 179, 0, 0, 0, 63, 0, 0, 64, 63, 92, 143, 2, 63, 0, 0, 128, 63, 0, 0, 0, 63, 0, 0, 128, 179, 0, 0, 0, 0, 0, 0, 128, 62, 92, 143, 2, 63, 0, 0, 128, 63, 0, 0, 0, 0, 170, 170, 42, 63, 0, 0, 0, 63, 103, 53, 117, 63, 82, 184, 94, 63, 170, 170, 42, 63, 0, 0, 0, 63, 170, 170, 42, 63, 0, 0, 0, 0, 206, 106, 234, 62, 82, 184, 94, 63, 170, 170, 42, 63, 0, 0, 0, 0, 169, 170, 170, 62, 0, 0, 0, 63, 169, 170, 170, 62, 0, 0, 0, 63, 153, 202, 10, 63, 82, 184, 94, 63, 169, 170, 170, 62, 0, 0, 0, 0, 169, 170, 170, 62, 0, 0, 0, 0, 144, 169, 44, 61, 82, 184, 94, 63), 22 | "format": 4119, 23 | "index_count": 24, 24 | "index_data": PackedByteArray(2, 0, 11, 0, 5, 0, 2, 0, 8, 0, 11, 0, 4, 0, 10, 0, 17, 0, 12, 0, 3, 0, 15, 0, 12, 0, 0, 0, 3, 0, 1, 0, 14, 0, 7, 0, 6, 0, 16, 0, 9, 0, 6, 0, 13, 0, 16, 0), 25 | "material": ExtResource("1_cu231"), 26 | "primitive": 3, 27 | "vertex_count": 18, 28 | "vertex_data": PackedByteArray(228, 15, 181, 178, 249, 223, 204, 188, 39, 83, 245, 190, 0, 0, 146, 153, 108, 102, 255, 191, 228, 15, 181, 178, 249, 223, 204, 188, 39, 83, 245, 190, 255, 127, 0, 0, 254, 255, 255, 63, 228, 15, 181, 178, 249, 223, 204, 188, 39, 83, 245, 190, 255, 255, 146, 153, 0, 0, 53, 243, 228, 15, 181, 178, 249, 223, 204, 60, 39, 83, 245, 190, 0, 0, 146, 153, 108, 102, 255, 191, 228, 15, 181, 178, 249, 223, 204, 60, 39, 83, 245, 190, 255, 127, 255, 255, 254, 255, 255, 191, 228, 15, 181, 178, 249, 223, 204, 60, 39, 83, 245, 190, 255, 255, 146, 153, 0, 0, 53, 243, 11, 130, 76, 62, 249, 223, 204, 188, 248, 50, 164, 62, 255, 127, 255, 127, 254, 255, 255, 191, 11, 130, 76, 62, 249, 223, 204, 188, 248, 50, 164, 62, 255, 127, 0, 0, 254, 255, 255, 63, 11, 130, 76, 62, 249, 223, 204, 188, 248, 50, 164, 62, 255, 255, 146, 153, 0, 0, 53, 243, 11, 130, 76, 62, 249, 223, 204, 60, 248, 50, 164, 62, 255, 127, 255, 127, 254, 255, 255, 191, 11, 130, 76, 62, 249, 223, 204, 60, 248, 50, 164, 62, 255, 127, 255, 255, 254, 255, 255, 191, 11, 130, 76, 62, 249, 223, 204, 60, 248, 50, 164, 62, 255, 255, 146, 153, 0, 0, 53, 243, 17, 130, 76, 190, 249, 223, 204, 188, 245, 50, 164, 62, 0, 0, 146, 153, 108, 102, 255, 191, 17, 130, 76, 190, 249, 223, 204, 188, 245, 50, 164, 62, 255, 127, 255, 127, 254, 255, 255, 191, 17, 130, 76, 190, 249, 223, 204, 188, 245, 50, 164, 62, 255, 127, 0, 0, 254, 255, 255, 63, 17, 130, 76, 190, 249, 223, 204, 60, 245, 50, 164, 62, 0, 0, 146, 153, 108, 102, 255, 191, 17, 130, 76, 190, 249, 223, 204, 60, 245, 50, 164, 62, 255, 127, 255, 127, 254, 255, 255, 191, 17, 130, 76, 190, 249, 223, 204, 60, 245, 50, 164, 62, 255, 127, 255, 255, 254, 255, 255, 191) 29 | }] 30 | blend_shape_mode = 0 31 | shadow_mesh = SubResource("ArrayMesh_4v5ep") 32 | -------------------------------------------------------------------------------- /addons/path-tool/path_node/path_pointer/path_pointer_3d.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name PathPointer3D 3 | extends MeshInstance3D 4 | 5 | var interpolation_positions:Array[Vector3] = [] 6 | 7 | var straight_interpolation_step_size:float 8 | var curve_interpolation_step_size:float 9 | var step_size_rounding:int 10 | var interpolation_mesh:Mesh = preload("res://addons/path-tool/path_node/path_pointer/interp_mesh.tres") 11 | 12 | 13 | enum CURVE_MODES { 14 | Automatic = 0, 15 | Enabled = 1, 16 | Disabled = 2 17 | } 18 | 19 | enum CURVE_FLIPPINGS { 20 | Automatic = 0, 21 | CurveRight = 1, 22 | CurveLeft = 2 23 | } 24 | 25 | @export var curve_mode:CURVE_MODES = 0 26 | @export_range(0, 65535, 1) var weight = 10 27 | @export_range(0.001, 10.0) var curve_value = 0.5 28 | @export var curve_flipping:CURVE_FLIPPINGS = 0 29 | @export_range(0, 100, 0.01) var custom_straight_interp_step_size = 0 30 | @export_range(0, 100, 0.01) var custom_curve_interp_step_size = 0 31 | @export var target_name:String 32 | 33 | var from_node:MeshInstance3D 34 | var from:Vector3 35 | var to:Vector3 36 | 37 | var curve_left := false 38 | var is_curve := false 39 | var helper_pos:Vector3 40 | 41 | var multi_mesh_instance := MultiMeshInstance3D.new() 42 | var multi_mesh := MultiMesh.new() 43 | 44 | 45 | func _enter_tree(): 46 | if get_child_count(true) == 0: 47 | add_child(multi_mesh_instance, true, 1) 48 | multi_mesh.transform_format = MultiMesh.TRANSFORM_3D 49 | multi_mesh.mesh = interpolation_mesh 50 | multi_mesh_instance.multimesh = multi_mesh 51 | multi_mesh_instance.cast_shadow = SHADOW_CASTING_SETTING_OFF 52 | 53 | 54 | func _ready(): 55 | if not is_in_group("path_pointers"): 56 | add_to_group("path_pointers") 57 | 58 | get_tree().call_group("path_tool_dock", "request_step_size_data", self) 59 | 60 | 61 | func init(_target_name:String, color:Color) -> void: 62 | set_owner(get_tree().get_edited_scene_root()) 63 | set_name("PathPointer3D") 64 | mesh = CylinderMesh.new() 65 | mesh.resource_local_to_scene = true 66 | mesh.top_radius = 0.1 67 | mesh.bottom_radius = 0.6 68 | mesh.radial_segments = 4 69 | mesh.rings = 1 70 | mesh.material = StandardMaterial3D.new() 71 | mesh.material.set_shading_mode(BaseMaterial3D.SHADING_MODE_UNSHADED) 72 | mesh.material.resource_local_to_scene = true 73 | mesh.material.albedo_color = color 74 | target_name = _target_name 75 | cast_shadow = SHADOW_CASTING_SETTING_OFF 76 | transparency = 0.2 77 | 78 | 79 | func update_step_size_data(straight_step, curve_step, step_rounding) -> void: 80 | straight_interpolation_step_size = straight_step 81 | curve_interpolation_step_size = curve_step 82 | step_size_rounding = step_rounding 83 | update_interpolation() 84 | 85 | 86 | func update_position(_from_node:MeshInstance3D, _to_node:MeshInstance3D) -> void: 87 | if _from_node.global_transform.origin == _to_node.global_transform.origin: 88 | return 89 | 90 | from_node = _from_node 91 | from = _from_node.global_transform.origin 92 | to = _to_node.global_transform.origin 93 | global_transform = get_parent().global_transform 94 | mesh.height = from.distance_to(to) 95 | look_at(to) 96 | rotate_object_local(Vector3.LEFT, PI/2) 97 | translate_object_local(Vector3.UP * (mesh.height/2)) 98 | 99 | update_interpolation() 100 | 101 | 102 | func update_interpolation(): 103 | if not is_instance_valid(from_node): 104 | return 105 | 106 | interpolation_positions.clear() 107 | 108 | match curve_mode: 109 | CURVE_MODES.Automatic: 110 | is_curve = !(absf(from.x - to.x) <= 0.5 or absf(from.z - to.z) <= 0.5) 111 | 112 | CURVE_MODES.Enabled: 113 | is_curve = true 114 | 115 | CURVE_MODES.Disabled: 116 | is_curve = false 117 | 118 | var step_size:float 119 | 120 | if is_curve: 121 | match curve_flipping: 122 | CURVE_FLIPPINGS.Automatic: 123 | curve_left = get_auto_curvature() 124 | 125 | CURVE_FLIPPINGS.CurveLeft: 126 | curve_left = true 127 | 128 | CURVE_FLIPPINGS.CurveRight: 129 | curve_left = false 130 | 131 | if custom_curve_interp_step_size > 0.0: 132 | step_size = custom_curve_interp_step_size 133 | else: 134 | step_size = curve_interpolation_step_size 135 | 136 | else: 137 | if custom_straight_interp_step_size > 0.0: 138 | step_size = custom_straight_interp_step_size 139 | else: 140 | step_size = straight_interpolation_step_size 141 | 142 | var full_distance:float = from.distance_to(to) 143 | if full_distance < step_size: 144 | multi_mesh_instance.hide() 145 | return 146 | 147 | var lerp_nodes_amount:int= ceili(full_distance/step_size) 148 | if step_size_rounding == 0: 149 | lerp_nodes_amount = floori(full_distance/step_size) 150 | else: 151 | lerp_nodes_amount = ceili(full_distance/step_size) 152 | 153 | if lerp_nodes_amount == 0: 154 | multi_mesh_instance.hide() 155 | return 156 | else: 157 | multi_mesh_instance.show() 158 | 159 | multi_mesh.instance_count = lerp_nodes_amount 160 | 161 | var relative_from:Vector3 162 | var relative_to:Vector3 163 | var interval_weight:float = 1.0/(lerp_nodes_amount) 164 | var current_weight:float = 0.0 165 | 166 | for i in lerp_nodes_amount: 167 | var mesh_pos:Vector3 168 | if is_curve: 169 | helper_pos = global_transform.translated_local(Vector3(curve_value * (-1 if curve_left else 1) * full_distance, 0, 0)).origin 170 | relative_from = from - helper_pos 171 | relative_to = to - helper_pos 172 | mesh_pos = relative_from.slerp(relative_to, current_weight) + helper_pos 173 | else: 174 | mesh_pos = from.lerp(to, current_weight) 175 | 176 | interpolation_positions.append(mesh_pos) 177 | current_weight += interval_weight 178 | 179 | for i in range(lerp_nodes_amount): 180 | var tform := Transform3D.IDENTITY 181 | var pos:Vector3 = interpolation_positions[i] + Vector3.UP * 0.6 182 | tform.origin = multi_mesh_instance.to_local(pos) 183 | 184 | var look_at_pos:Vector3 185 | if i < lerp_nodes_amount-1: 186 | look_at_pos = interpolation_positions[i+1] + Vector3.UP * 0.6 187 | else: 188 | look_at_pos = to + Vector3.UP * 0.6 189 | 190 | # weird thing where the middle interp node fails the lookint_at() 191 | if not is_curve or (lerp_nodes_amount % 2 == 1 and i == (lerp_nodes_amount-1)/2): 192 | tform = tform.rotated_local(Vector3.RIGHT, PI/2) 193 | else: 194 | tform = tform.looking_at(multi_mesh_instance.to_local(look_at_pos)) 195 | tform = tform.rotated_local(Vector3.FORWARD, PI/2) 196 | 197 | multi_mesh.set_instance_transform(i, tform) 198 | 199 | 200 | func get_auto_curvature() -> bool: 201 | if from_node.previous_neighbours.is_empty(): 202 | return curve_flipping == CURVE_FLIPPINGS.CurveLeft 203 | 204 | var prev_neighbour = from_node.get_prev_neighbour(from_node.previous_neighbours[0]) 205 | 206 | if not prev_neighbour: 207 | return curve_flipping == CURVE_FLIPPINGS.CurveLeft 208 | 209 | prev_neighbour.look_at(to, Vector3.UP) 210 | var result:bool = prev_neighbour.to_local(from).x > 0 211 | prev_neighbour.rotation = Vector3.ZERO 212 | return result 213 | -------------------------------------------------------------------------------- /addons/path-tool/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="path-tool" 4 | description="Custom node to make paths and export the data as CSV" 5 | author="Eerik Hirvonen" 6 | version="1.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/path-tool/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | var dock:Control 5 | 6 | func _enter_tree(): 7 | dock=preload("../path-tool/dock/path_tool_dock.tscn").instantiate() 8 | add_control_to_dock(DOCK_SLOT_LEFT_UR, dock) 9 | 10 | var icon:Texture2D = get_editor_interface().get_base_control().get_theme_icon("Path3D", "EditorIcons") 11 | 12 | add_custom_type( 13 | "PathPoint3D", 14 | "MeshInstance3D", 15 | preload("path_node/path_point_3d.gd"), 16 | icon 17 | ) 18 | 19 | add_custom_type( 20 | "PathPointManager", 21 | "Node3D", 22 | preload("path_node/path_point_manager.gd"), 23 | icon 24 | ) 25 | 26 | if not InputMap.has_action("place_super_node"): 27 | InputMap.add_action("place_super_node") 28 | var event := InputEventKey.new() 29 | event.physical_keycode = KEY_SPACE 30 | InputMap.action_add_event("place_super_node", event) 31 | 32 | 33 | func _exit_tree(): 34 | remove_control_from_docks(dock) 35 | dock.free() 36 | 37 | remove_custom_type("PathPoint3D") 38 | remove_custom_type("PathPointManager") 39 | -------------------------------------------------------------------------------- /addons/path-tool/save_resource.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name SaveResource 3 | 4 | const save_path := "user://path_tool_save_file.tres" 5 | 6 | @export var straight_interpolation_step_size:float 7 | @export var curve_interpolation_step_size:float 8 | @export var step_size_rounding:int 9 | 10 | func _init(straight:float, curve:float, rounding:int): 11 | straight_interpolation_step_size = straight 12 | curve_interpolation_step_size = curve 13 | step_size_rounding = rounding 14 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | _global_script_classes=[{ 12 | "base": "MeshInstance3D", 13 | "class": &"PathPoint3D", 14 | "language": &"GDScript", 15 | "path": "res://addons/path-tool/path_node/path_point_3d.gd" 16 | }, { 17 | "base": "Node3D", 18 | "class": &"PathPointManager", 19 | "language": &"GDScript", 20 | "path": "res://addons/path-tool/path_node/path_point_manager.gd" 21 | }, { 22 | "base": "MeshInstance3D", 23 | "class": &"PathPointer3D", 24 | "language": &"GDScript", 25 | "path": "res://addons/path-tool/path_node/path_pointer/path_pointer_3d.gd" 26 | }, { 27 | "base": "Resource", 28 | "class": &"SaveResource", 29 | "language": &"GDScript", 30 | "path": "res://addons/path-tool/save_resource.gd" 31 | }] 32 | _global_script_class_icons={ 33 | "PathPoint3D": "", 34 | "PathPointManager": "", 35 | "PathPointer3D": "", 36 | "SaveResource": "" 37 | } 38 | 39 | [application] 40 | 41 | config/name="Path Tool" 42 | run/main_scene="res://test_scene/test_scene.tscn" 43 | config/features=PackedStringArray("4.0") 44 | 45 | [editor_plugins] 46 | 47 | enabled=PackedStringArray("res://addons/path-tool/plugin.cfg") 48 | 49 | [gui] 50 | 51 | common/drop_mouse_on_gui_input_disabled=true 52 | 53 | [physics] 54 | 55 | common/enable_pause_aware_picking=true 56 | 57 | [rendering] 58 | 59 | environment/default_environment="res://default_env.tres" 60 | -------------------------------------------------------------------------------- /test_scene.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=116 format=3 uid="uid://bq5vinkauhl2y"] 2 | 3 | [ext_resource type="Script" path="res://addons/path-tool/path_node/path_point_manager.gd" id="2_3jgq3"] 4 | [ext_resource type="Script" path="res://addons/path-tool/path_node/path_point_3d.gd" id="3_g5rit"] 5 | [ext_resource type="Script" path="res://addons/path-tool/path_node/path_pointer/path_pointer_3d.gd" id="4_h60ui"] 6 | 7 | [sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_6k6lp"] 8 | 9 | [sub_resource type="Gradient" id="Gradient_w6ikx"] 10 | offsets = PackedFloat32Array(0.212575, 0.799401) 11 | colors = PackedColorArray(0.472656, 0.472656, 0.472656, 1, 0.554688, 0.554688, 0.554688, 1) 12 | 13 | [sub_resource type="FastNoiseLite" id="FastNoiseLite_0of56"] 14 | frequency = 0.02 15 | 16 | [sub_resource type="NoiseTexture2D" id="NoiseTexture2D_5udds"] 17 | color_ramp = SubResource("Gradient_w6ikx") 18 | noise = SubResource("FastNoiseLite_0of56") 19 | 20 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_n522r"] 21 | albedo_texture = SubResource("NoiseTexture2D_5udds") 22 | 23 | [sub_resource type="PlaneMesh" id="PlaneMesh_rkbrm"] 24 | material = SubResource("StandardMaterial3D_n522r") 25 | size = Vector2(42, 42) 26 | 27 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ynycs"] 28 | shading_mode = 0 29 | albedo_color = Color(0, 1, 0, 1) 30 | 31 | [sub_resource type="SphereMesh" id="SphereMesh_jtrpe"] 32 | material = SubResource("StandardMaterial3D_ynycs") 33 | radius = 0.7 34 | height = 0.7 35 | radial_segments = 6 36 | rings = 3 37 | is_hemisphere = true 38 | 39 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_k7o6d"] 40 | resource_local_to_scene = true 41 | shading_mode = 0 42 | albedo_color = Color(0.117647, 0.564706, 1, 1) 43 | 44 | [sub_resource type="CylinderMesh" id="CylinderMesh_vicjo"] 45 | resource_local_to_scene = true 46 | material = SubResource("StandardMaterial3D_k7o6d") 47 | top_radius = 0.1 48 | bottom_radius = 0.6 49 | height = 11.3137 50 | radial_segments = 4 51 | rings = 1 52 | 53 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_e1ew5"] 54 | shading_mode = 0 55 | albedo_color = Color(0, 1, 0, 1) 56 | 57 | [sub_resource type="SphereMesh" id="SphereMesh_cp4eo"] 58 | material = SubResource("StandardMaterial3D_e1ew5") 59 | radius = 0.7 60 | height = 0.7 61 | radial_segments = 6 62 | rings = 3 63 | is_hemisphere = true 64 | 65 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jidvr"] 66 | resource_local_to_scene = true 67 | shading_mode = 0 68 | albedo_color = Color(0.117647, 0.564706, 1, 1) 69 | 70 | [sub_resource type="CylinderMesh" id="CylinderMesh_my0j3"] 71 | resource_local_to_scene = true 72 | material = SubResource("StandardMaterial3D_jidvr") 73 | top_radius = 0.1 74 | bottom_radius = 0.6 75 | height = 16.0 76 | radial_segments = 4 77 | rings = 1 78 | 79 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_b82np"] 80 | shading_mode = 0 81 | albedo_color = Color(0, 1, 0, 1) 82 | 83 | [sub_resource type="SphereMesh" id="SphereMesh_gw04c"] 84 | material = SubResource("StandardMaterial3D_b82np") 85 | radius = 0.7 86 | height = 0.7 87 | radial_segments = 6 88 | rings = 3 89 | is_hemisphere = true 90 | 91 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8ox46"] 92 | resource_local_to_scene = true 93 | shading_mode = 0 94 | albedo_color = Color(0.117647, 0.564706, 1, 1) 95 | 96 | [sub_resource type="CylinderMesh" id="CylinderMesh_q5bky"] 97 | resource_local_to_scene = true 98 | material = SubResource("StandardMaterial3D_8ox46") 99 | top_radius = 0.1 100 | bottom_radius = 0.6 101 | height = 11.3137 102 | radial_segments = 4 103 | rings = 1 104 | 105 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6uw48"] 106 | shading_mode = 0 107 | albedo_color = Color(0, 1, 0, 1) 108 | 109 | [sub_resource type="SphereMesh" id="SphereMesh_ikbkp"] 110 | material = SubResource("StandardMaterial3D_6uw48") 111 | radius = 0.7 112 | height = 0.7 113 | radial_segments = 6 114 | rings = 3 115 | is_hemisphere = true 116 | 117 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_b34ry"] 118 | resource_local_to_scene = true 119 | shading_mode = 0 120 | albedo_color = Color(0.117647, 0.564706, 1, 1) 121 | 122 | [sub_resource type="CylinderMesh" id="CylinderMesh_011wo"] 123 | resource_local_to_scene = true 124 | material = SubResource("StandardMaterial3D_b34ry") 125 | top_radius = 0.1 126 | bottom_radius = 0.6 127 | height = 29.0 128 | radial_segments = 4 129 | rings = 1 130 | 131 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7gnk7"] 132 | shading_mode = 0 133 | albedo_color = Color(1, 1, 0, 1) 134 | 135 | [sub_resource type="SphereMesh" id="SphereMesh_1r37a"] 136 | material = SubResource("StandardMaterial3D_7gnk7") 137 | radius = 0.7 138 | height = 0.7 139 | radial_segments = 6 140 | rings = 3 141 | is_hemisphere = true 142 | 143 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_u5pxj"] 144 | shading_mode = 0 145 | albedo_color = Color(0.627451, 0.12549, 0.941176, 1) 146 | 147 | [sub_resource type="SphereMesh" id="SphereMesh_b4dh4"] 148 | material = SubResource("StandardMaterial3D_u5pxj") 149 | radius = 0.7 150 | height = 0.7 151 | radial_segments = 6 152 | rings = 3 153 | is_hemisphere = true 154 | 155 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_r4s2f"] 156 | resource_local_to_scene = true 157 | shading_mode = 0 158 | albedo_color = Color(0.117647, 0.564706, 1, 1) 159 | 160 | [sub_resource type="CylinderMesh" id="CylinderMesh_f6d61"] 161 | resource_local_to_scene = true 162 | material = SubResource("StandardMaterial3D_r4s2f") 163 | top_radius = 0.1 164 | bottom_radius = 0.6 165 | height = 29.0 166 | radial_segments = 4 167 | rings = 1 168 | 169 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_t0715"] 170 | shading_mode = 0 171 | albedo_color = Color(0.627451, 0.12549, 0.941176, 1) 172 | 173 | [sub_resource type="SphereMesh" id="SphereMesh_hieia"] 174 | material = SubResource("StandardMaterial3D_t0715") 175 | radius = 0.7 176 | height = 0.7 177 | radial_segments = 6 178 | rings = 3 179 | is_hemisphere = true 180 | 181 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_et0xo"] 182 | resource_local_to_scene = true 183 | shading_mode = 0 184 | albedo_color = Color(0.117647, 0.564706, 1, 1) 185 | 186 | [sub_resource type="CylinderMesh" id="CylinderMesh_ar5bg"] 187 | resource_local_to_scene = true 188 | material = SubResource("StandardMaterial3D_et0xo") 189 | top_radius = 0.1 190 | bottom_radius = 0.6 191 | height = 29.0 192 | radial_segments = 4 193 | rings = 1 194 | 195 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_qrfar"] 196 | shading_mode = 0 197 | albedo_color = Color(0, 1, 0, 1) 198 | 199 | [sub_resource type="SphereMesh" id="SphereMesh_mklxc"] 200 | material = SubResource("StandardMaterial3D_qrfar") 201 | radius = 0.7 202 | height = 0.7 203 | radial_segments = 6 204 | rings = 3 205 | is_hemisphere = true 206 | 207 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_31lnu"] 208 | resource_local_to_scene = true 209 | shading_mode = 0 210 | albedo_color = Color(0.117647, 0.564706, 1, 1) 211 | 212 | [sub_resource type="CylinderMesh" id="CylinderMesh_7oisk"] 213 | resource_local_to_scene = true 214 | material = SubResource("StandardMaterial3D_31lnu") 215 | top_radius = 0.1 216 | bottom_radius = 0.6 217 | height = 5.65685 218 | radial_segments = 4 219 | rings = 1 220 | 221 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8lxmk"] 222 | shading_mode = 0 223 | albedo_color = Color(0, 1, 0, 1) 224 | 225 | [sub_resource type="SphereMesh" id="SphereMesh_5vury"] 226 | material = SubResource("StandardMaterial3D_8lxmk") 227 | radius = 0.7 228 | height = 0.7 229 | radial_segments = 6 230 | rings = 3 231 | is_hemisphere = true 232 | 233 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bk7k7"] 234 | resource_local_to_scene = true 235 | shading_mode = 0 236 | albedo_color = Color(0.117647, 0.564706, 1, 1) 237 | 238 | [sub_resource type="CylinderMesh" id="CylinderMesh_7slvl"] 239 | resource_local_to_scene = true 240 | material = SubResource("StandardMaterial3D_bk7k7") 241 | top_radius = 0.1 242 | bottom_radius = 0.6 243 | height = 16.0 244 | radial_segments = 4 245 | rings = 1 246 | 247 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hklmd"] 248 | shading_mode = 0 249 | albedo_color = Color(0, 1, 0, 1) 250 | 251 | [sub_resource type="SphereMesh" id="SphereMesh_gvtmo"] 252 | material = SubResource("StandardMaterial3D_hklmd") 253 | radius = 0.7 254 | height = 0.7 255 | radial_segments = 6 256 | rings = 3 257 | is_hemisphere = true 258 | 259 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kxnwq"] 260 | resource_local_to_scene = true 261 | shading_mode = 0 262 | albedo_color = Color(0.117647, 0.564706, 1, 1) 263 | 264 | [sub_resource type="CylinderMesh" id="CylinderMesh_kjyso"] 265 | resource_local_to_scene = true 266 | material = SubResource("StandardMaterial3D_kxnwq") 267 | top_radius = 0.1 268 | bottom_radius = 0.6 269 | height = 5.65685 270 | radial_segments = 4 271 | rings = 1 272 | 273 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_q2042"] 274 | shading_mode = 0 275 | albedo_color = Color(0, 1, 0, 1) 276 | 277 | [sub_resource type="SphereMesh" id="SphereMesh_pb0eo"] 278 | material = SubResource("StandardMaterial3D_q2042") 279 | radius = 0.7 280 | height = 0.7 281 | radial_segments = 6 282 | rings = 3 283 | is_hemisphere = true 284 | 285 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_63sla"] 286 | resource_local_to_scene = true 287 | shading_mode = 0 288 | albedo_color = Color(0.117647, 0.564706, 1, 1) 289 | 290 | [sub_resource type="CylinderMesh" id="CylinderMesh_prdcg"] 291 | resource_local_to_scene = true 292 | material = SubResource("StandardMaterial3D_63sla") 293 | top_radius = 0.1 294 | bottom_radius = 0.6 295 | height = 29.0 296 | radial_segments = 4 297 | rings = 1 298 | 299 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_368eh"] 300 | shading_mode = 0 301 | albedo_color = Color(1, 1, 0, 1) 302 | 303 | [sub_resource type="SphereMesh" id="SphereMesh_vk67i"] 304 | material = SubResource("StandardMaterial3D_368eh") 305 | radius = 0.7 306 | height = 0.7 307 | radial_segments = 6 308 | rings = 3 309 | is_hemisphere = true 310 | 311 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vvwpw"] 312 | shading_mode = 0 313 | albedo_color = Color(0.627451, 0.12549, 0.941176, 1) 314 | 315 | [sub_resource type="SphereMesh" id="SphereMesh_hyeyr"] 316 | material = SubResource("StandardMaterial3D_vvwpw") 317 | radius = 0.7 318 | height = 0.7 319 | radial_segments = 6 320 | rings = 3 321 | is_hemisphere = true 322 | 323 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kwq2q"] 324 | resource_local_to_scene = true 325 | shading_mode = 0 326 | albedo_color = Color(1, 0.843137, 0, 1) 327 | 328 | [sub_resource type="CylinderMesh" id="CylinderMesh_7rmrx"] 329 | resource_local_to_scene = true 330 | material = SubResource("StandardMaterial3D_kwq2q") 331 | top_radius = 0.1 332 | bottom_radius = 0.6 333 | height = 15.0 334 | radial_segments = 4 335 | rings = 1 336 | 337 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_i5v77"] 338 | shading_mode = 0 339 | albedo_color = Color(0, 1, 0, 1) 340 | 341 | [sub_resource type="SphereMesh" id="SphereMesh_c8edd"] 342 | material = SubResource("StandardMaterial3D_i5v77") 343 | radius = 0.7 344 | height = 0.7 345 | radial_segments = 6 346 | rings = 3 347 | is_hemisphere = true 348 | 349 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uvnu4"] 350 | resource_local_to_scene = true 351 | shading_mode = 0 352 | albedo_color = Color(1, 0.843137, 0, 1) 353 | 354 | [sub_resource type="CylinderMesh" id="CylinderMesh_8di87"] 355 | resource_local_to_scene = true 356 | material = SubResource("StandardMaterial3D_uvnu4") 357 | top_radius = 0.1 358 | bottom_radius = 0.6 359 | height = 5.65685 360 | radial_segments = 4 361 | rings = 1 362 | 363 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_iuukh"] 364 | shading_mode = 0 365 | albedo_color = Color(0, 1, 0, 1) 366 | 367 | [sub_resource type="SphereMesh" id="SphereMesh_digi8"] 368 | material = SubResource("StandardMaterial3D_iuukh") 369 | radius = 0.7 370 | height = 0.7 371 | radial_segments = 6 372 | rings = 3 373 | is_hemisphere = true 374 | 375 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_l2xo4"] 376 | resource_local_to_scene = true 377 | shading_mode = 0 378 | albedo_color = Color(1, 0.843137, 0, 1) 379 | 380 | [sub_resource type="CylinderMesh" id="CylinderMesh_lu3in"] 381 | resource_local_to_scene = true 382 | material = SubResource("StandardMaterial3D_l2xo4") 383 | top_radius = 0.1 384 | bottom_radius = 0.6 385 | height = 15.0 386 | radial_segments = 4 387 | rings = 1 388 | 389 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_fyb3g"] 390 | shading_mode = 0 391 | albedo_color = Color(1, 1, 0, 1) 392 | 393 | [sub_resource type="SphereMesh" id="SphereMesh_ku5xk"] 394 | material = SubResource("StandardMaterial3D_fyb3g") 395 | radius = 0.7 396 | height = 0.7 397 | radial_segments = 6 398 | rings = 3 399 | is_hemisphere = true 400 | 401 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_5d0x6"] 402 | shading_mode = 0 403 | albedo_color = Color(0.627451, 0.12549, 0.941176, 1) 404 | 405 | [sub_resource type="SphereMesh" id="SphereMesh_sourp"] 406 | material = SubResource("StandardMaterial3D_5d0x6") 407 | radius = 0.7 408 | height = 0.7 409 | radial_segments = 6 410 | rings = 3 411 | is_hemisphere = true 412 | 413 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_u155h"] 414 | resource_local_to_scene = true 415 | shading_mode = 0 416 | albedo_color = Color(1, 0.843137, 0, 1) 417 | 418 | [sub_resource type="CylinderMesh" id="CylinderMesh_quwpm"] 419 | resource_local_to_scene = true 420 | material = SubResource("StandardMaterial3D_u155h") 421 | top_radius = 0.1 422 | bottom_radius = 0.6 423 | height = 15.0 424 | radial_segments = 4 425 | rings = 1 426 | 427 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_graka"] 428 | shading_mode = 0 429 | albedo_color = Color(0, 1, 0, 1) 430 | 431 | [sub_resource type="SphereMesh" id="SphereMesh_jgccw"] 432 | material = SubResource("StandardMaterial3D_graka") 433 | radius = 0.7 434 | height = 0.7 435 | radial_segments = 6 436 | rings = 3 437 | is_hemisphere = true 438 | 439 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yw7ff"] 440 | resource_local_to_scene = true 441 | shading_mode = 0 442 | albedo_color = Color(1, 0.843137, 0, 1) 443 | 444 | [sub_resource type="CylinderMesh" id="CylinderMesh_gxud7"] 445 | resource_local_to_scene = true 446 | material = SubResource("StandardMaterial3D_yw7ff") 447 | top_radius = 0.1 448 | bottom_radius = 0.6 449 | height = 11.3137 450 | radial_segments = 4 451 | rings = 1 452 | 453 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_al8bu"] 454 | shading_mode = 0 455 | albedo_color = Color(0, 1, 0, 1) 456 | 457 | [sub_resource type="SphereMesh" id="SphereMesh_r182t"] 458 | material = SubResource("StandardMaterial3D_al8bu") 459 | radius = 0.7 460 | height = 0.7 461 | radial_segments = 6 462 | rings = 3 463 | is_hemisphere = true 464 | 465 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_55q0h"] 466 | resource_local_to_scene = true 467 | shading_mode = 0 468 | albedo_color = Color(1, 0.843137, 0, 1) 469 | 470 | [sub_resource type="CylinderMesh" id="CylinderMesh_sgbka"] 471 | resource_local_to_scene = true 472 | material = SubResource("StandardMaterial3D_55q0h") 473 | top_radius = 0.1 474 | bottom_radius = 0.6 475 | height = 15.0 476 | radial_segments = 4 477 | rings = 1 478 | 479 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7n1rf"] 480 | shading_mode = 0 481 | albedo_color = Color(1, 1, 0, 1) 482 | 483 | [sub_resource type="SphereMesh" id="SphereMesh_vjdso"] 484 | material = SubResource("StandardMaterial3D_7n1rf") 485 | radius = 0.7 486 | height = 0.7 487 | radial_segments = 6 488 | rings = 3 489 | is_hemisphere = true 490 | 491 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hhamo"] 492 | shading_mode = 0 493 | albedo_color = Color(0, 1, 0, 1) 494 | 495 | [sub_resource type="SphereMesh" id="SphereMesh_tmfmm"] 496 | material = SubResource("StandardMaterial3D_hhamo") 497 | radius = 0.7 498 | height = 0.7 499 | radial_segments = 6 500 | rings = 3 501 | is_hemisphere = true 502 | 503 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dc4tl"] 504 | resource_local_to_scene = true 505 | shading_mode = 0 506 | albedo_color = Color(0.576471, 0.439216, 0.858824, 1) 507 | 508 | [sub_resource type="CylinderMesh" id="CylinderMesh_7tjst"] 509 | resource_local_to_scene = true 510 | material = SubResource("StandardMaterial3D_dc4tl") 511 | top_radius = 0.1 512 | bottom_radius = 0.6 513 | height = 18.0 514 | radial_segments = 4 515 | rings = 1 516 | 517 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_d2x83"] 518 | shading_mode = 0 519 | albedo_color = Color(0, 1, 0, 1) 520 | 521 | [sub_resource type="SphereMesh" id="SphereMesh_i8wlo"] 522 | material = SubResource("StandardMaterial3D_d2x83") 523 | radius = 0.7 524 | height = 0.7 525 | radial_segments = 6 526 | rings = 3 527 | is_hemisphere = true 528 | 529 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_4nwkw"] 530 | resource_local_to_scene = true 531 | shading_mode = 0 532 | albedo_color = Color(0.576471, 0.439216, 0.858824, 1) 533 | 534 | [sub_resource type="CylinderMesh" id="CylinderMesh_du4bb"] 535 | resource_local_to_scene = true 536 | material = SubResource("StandardMaterial3D_4nwkw") 537 | top_radius = 0.1 538 | bottom_radius = 0.6 539 | height = 4.0 540 | radial_segments = 4 541 | rings = 1 542 | 543 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bfwtp"] 544 | shading_mode = 0 545 | albedo_color = Color(0, 1, 0, 1) 546 | 547 | [sub_resource type="SphereMesh" id="SphereMesh_peiox"] 548 | material = SubResource("StandardMaterial3D_bfwtp") 549 | radius = 0.7 550 | height = 0.7 551 | radial_segments = 6 552 | rings = 3 553 | is_hemisphere = true 554 | 555 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3dx53"] 556 | resource_local_to_scene = true 557 | shading_mode = 0 558 | albedo_color = Color(0.576471, 0.439216, 0.858824, 1) 559 | 560 | [sub_resource type="CylinderMesh" id="CylinderMesh_fytsi"] 561 | resource_local_to_scene = true 562 | material = SubResource("StandardMaterial3D_3dx53") 563 | top_radius = 0.1 564 | bottom_radius = 0.6 565 | height = 18.0 566 | radial_segments = 4 567 | rings = 1 568 | 569 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jcgyv"] 570 | shading_mode = 0 571 | albedo_color = Color(0, 1, 0, 1) 572 | 573 | [sub_resource type="SphereMesh" id="SphereMesh_bu173"] 574 | material = SubResource("StandardMaterial3D_jcgyv") 575 | radius = 0.7 576 | height = 0.7 577 | radial_segments = 6 578 | rings = 3 579 | is_hemisphere = true 580 | 581 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jhf6e"] 582 | resource_local_to_scene = true 583 | shading_mode = 0 584 | albedo_color = Color(0.576471, 0.439216, 0.858824, 1) 585 | 586 | [sub_resource type="CylinderMesh" id="CylinderMesh_psicm"] 587 | resource_local_to_scene = true 588 | material = SubResource("StandardMaterial3D_jhf6e") 589 | top_radius = 0.1 590 | bottom_radius = 0.6 591 | height = 4.0 592 | radial_segments = 4 593 | rings = 1 594 | 595 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kqt0j"] 596 | shading_mode = 0 597 | albedo_color = Color(0, 1, 0, 1) 598 | 599 | [sub_resource type="SphereMesh" id="SphereMesh_p7i87"] 600 | material = SubResource("StandardMaterial3D_kqt0j") 601 | radius = 0.7 602 | height = 0.7 603 | radial_segments = 6 604 | rings = 3 605 | is_hemisphere = true 606 | 607 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_lad0q"] 608 | resource_local_to_scene = true 609 | shading_mode = 0 610 | albedo_color = Color(0.117647, 0.564706, 1, 1) 611 | 612 | [sub_resource type="CylinderMesh" id="CylinderMesh_iwm3j"] 613 | resource_local_to_scene = true 614 | material = SubResource("StandardMaterial3D_lad0q") 615 | top_radius = 0.1 616 | bottom_radius = 0.6 617 | height = 9.8995 618 | radial_segments = 4 619 | rings = 1 620 | 621 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2uxhu"] 622 | resource_local_to_scene = true 623 | shading_mode = 0 624 | albedo_color = Color(0.117647, 0.564706, 1, 1) 625 | 626 | [sub_resource type="CylinderMesh" id="CylinderMesh_cdnmf"] 627 | resource_local_to_scene = true 628 | material = SubResource("StandardMaterial3D_2uxhu") 629 | top_radius = 0.1 630 | bottom_radius = 0.6 631 | height = 7.0 632 | radial_segments = 4 633 | rings = 1 634 | 635 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_oext6"] 636 | resource_local_to_scene = true 637 | shading_mode = 0 638 | albedo_color = Color(0.117647, 0.564706, 1, 1) 639 | 640 | [sub_resource type="CylinderMesh" id="CylinderMesh_5oojn"] 641 | resource_local_to_scene = true 642 | material = SubResource("StandardMaterial3D_oext6") 643 | top_radius = 0.1 644 | bottom_radius = 0.6 645 | height = 9.8995 646 | radial_segments = 4 647 | rings = 1 648 | 649 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_qh35c"] 650 | shading_mode = 0 651 | albedo_color = Color(0.627451, 0.12549, 0.941176, 1) 652 | 653 | [sub_resource type="SphereMesh" id="SphereMesh_jec8p"] 654 | material = SubResource("StandardMaterial3D_qh35c") 655 | radius = 0.7 656 | height = 0.7 657 | radial_segments = 6 658 | rings = 3 659 | is_hemisphere = true 660 | 661 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_uy7js"] 662 | resource_local_to_scene = true 663 | shading_mode = 0 664 | albedo_color = Color(0.117647, 0.564706, 1, 1) 665 | 666 | [sub_resource type="CylinderMesh" id="CylinderMesh_q1lhp"] 667 | resource_local_to_scene = true 668 | material = SubResource("StandardMaterial3D_uy7js") 669 | top_radius = 0.1 670 | bottom_radius = 0.6 671 | height = 7.0 672 | radial_segments = 4 673 | rings = 1 674 | 675 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6y662"] 676 | shading_mode = 0 677 | albedo_color = Color(1, 1, 0, 1) 678 | 679 | [sub_resource type="SphereMesh" id="SphereMesh_yj0vn"] 680 | material = SubResource("StandardMaterial3D_6y662") 681 | radius = 0.7 682 | height = 0.7 683 | radial_segments = 6 684 | rings = 3 685 | is_hemisphere = true 686 | 687 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2ewmi"] 688 | shading_mode = 0 689 | albedo_color = Color(1, 1, 0, 1) 690 | 691 | [sub_resource type="SphereMesh" id="SphereMesh_kxdgj"] 692 | material = SubResource("StandardMaterial3D_2ewmi") 693 | radius = 0.7 694 | height = 0.7 695 | radial_segments = 6 696 | rings = 3 697 | is_hemisphere = true 698 | 699 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7fcmb"] 700 | shading_mode = 0 701 | albedo_color = Color(1, 1, 0, 1) 702 | 703 | [sub_resource type="SphereMesh" id="SphereMesh_jwsji"] 704 | material = SubResource("StandardMaterial3D_7fcmb") 705 | radius = 0.7 706 | height = 0.7 707 | radial_segments = 6 708 | rings = 3 709 | is_hemisphere = true 710 | 711 | [node name="TestScene" type="Node3D"] 712 | 713 | [node name="Camera3D" type="Camera3D" parent="."] 714 | transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 14, 26) 715 | 716 | [node name="Roads" type="Node3D" parent="."] 717 | metadata/_edit_group_ = true 718 | metadata/_edit_lock_ = true 719 | 720 | [node name="StaticBody3D" type="StaticBody3D" parent="Roads"] 721 | 722 | [node name="CollisionShape3D" type="CollisionShape3D" parent="Roads/StaticBody3D"] 723 | shape = SubResource("WorldBoundaryShape3D_6k6lp") 724 | 725 | [node name="GroundMesh" type="MeshInstance3D" parent="Roads"] 726 | mesh = SubResource("PlaneMesh_rkbrm") 727 | 728 | [node name="PathPointManager" type="Node3D" parent="."] 729 | script = ExtResource("2_3jgq3") 730 | 731 | [node name="25329110095139" type="MeshInstance3D" parent="PathPointManager"] 732 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 16) 733 | transparency = 0.2 734 | cast_shadow = 0 735 | mesh = SubResource("SphereMesh_jtrpe") 736 | script = ExtResource("3_g5rit") 737 | next_neighbours = ["25341186843100"] 738 | previous_neighbours = ["25375304077210"] 739 | 740 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25329110095139"] 741 | transform = Transform3D(0.707107, 0.707107, 3.09086e-08, 0, -4.37114e-08, 1, 0.707107, -0.707107, -3.09086e-08, 4, -2.47269e-07, -4) 742 | transparency = 0.2 743 | cast_shadow = 0 744 | mesh = SubResource("CylinderMesh_vicjo") 745 | script = ExtResource("4_h60ui") 746 | target_name = "25341186843100" 747 | 748 | [node name="25341186843100" type="MeshInstance3D" parent="PathPointManager"] 749 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16, 0, 8) 750 | transparency = 0.2 751 | cast_shadow = 0 752 | mesh = SubResource("SphereMesh_cp4eo") 753 | script = ExtResource("3_g5rit") 754 | next_neighbours = ["25349005853911"] 755 | previous_neighbours = ["25329110095139"] 756 | 757 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25341186843100"] 758 | transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, -3.49691e-07, -8) 759 | transparency = 0.2 760 | cast_shadow = 0 761 | mesh = SubResource("CylinderMesh_my0j3") 762 | script = ExtResource("4_h60ui") 763 | target_name = "25349005853911" 764 | 765 | [node name="25349005853911" type="MeshInstance3D" parent="PathPointManager"] 766 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16, 0, -8) 767 | transparency = 0.2 768 | cast_shadow = 0 769 | mesh = SubResource("SphereMesh_gw04c") 770 | script = ExtResource("3_g5rit") 771 | next_neighbours = ["25351885805161"] 772 | previous_neighbours = ["25341186843100"] 773 | 774 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25349005853911"] 775 | transform = Transform3D(0.707107, -0.707107, -3.09086e-08, 0, -4.37114e-08, 1, -0.707107, -0.707107, -3.09086e-08, -4, -2.47269e-07, -4) 776 | transparency = 0.2 777 | cast_shadow = 0 778 | mesh = SubResource("CylinderMesh_q5bky") 779 | script = ExtResource("4_h60ui") 780 | target_name = "25351885805161" 781 | 782 | [node name="25351885805161" type="MeshInstance3D" parent="PathPointManager"] 783 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, -16) 784 | transparency = 0.2 785 | cast_shadow = 0 786 | mesh = SubResource("SphereMesh_ikbkp") 787 | script = ExtResource("3_g5rit") 788 | next_neighbours = ["25358757210941"] 789 | previous_neighbours = ["25349005853911"] 790 | 791 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25351885805161"] 792 | transform = Transform3D(-4.37114e-08, -1, -4.37114e-08, 0, -4.37114e-08, 1, -1, 4.37114e-08, 1.91069e-15, -14.5, -6.33815e-07, 6.33815e-07) 793 | transparency = 0.2 794 | cast_shadow = 0 795 | mesh = SubResource("CylinderMesh_011wo") 796 | script = ExtResource("4_h60ui") 797 | target_name = "25358757210941" 798 | 799 | [node name="25358757210941" type="MeshInstance3D" parent="PathPointManager"] 800 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21, 0, -16) 801 | transparency = 0.2 802 | cast_shadow = 0 803 | mesh = SubResource("SphereMesh_1r37a") 804 | script = ExtResource("3_g5rit") 805 | next_neighbours = [] 806 | previous_neighbours = ["25351885805161"] 807 | 808 | [node name="25375304077210" type="MeshInstance3D" parent="PathPointManager"] 809 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21, 0, 16) 810 | transparency = 0.2 811 | cast_shadow = 0 812 | mesh = SubResource("SphereMesh_b4dh4") 813 | script = ExtResource("3_g5rit") 814 | next_neighbours = ["25329110095139"] 815 | previous_neighbours = [] 816 | 817 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25375304077210"] 818 | transform = Transform3D(-4.37114e-08, 1, 4.37114e-08, 0, -4.37114e-08, 1, 1, 4.37114e-08, 1.91069e-15, 14.5, -6.33815e-07, 6.33815e-07) 819 | transparency = 0.2 820 | cast_shadow = 0 821 | mesh = SubResource("CylinderMesh_f6d61") 822 | script = ExtResource("4_h60ui") 823 | target_name = "25329110095139" 824 | 825 | [node name="25401088514103" type="MeshInstance3D" parent="PathPointManager"] 826 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21, 0, -12) 827 | transparency = 0.2 828 | cast_shadow = 0 829 | mesh = SubResource("SphereMesh_hieia") 830 | script = ExtResource("3_g5rit") 831 | next_neighbours = ["25406946714102"] 832 | previous_neighbours = [] 833 | 834 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25401088514103"] 835 | transform = Transform3D(-4.37114e-08, 1, 4.37114e-08, 0, -4.37114e-08, 1, 1, 4.37114e-08, 1.91069e-15, 14.5, -6.33815e-07, 6.33815e-07) 836 | transparency = 0.2 837 | cast_shadow = 0 838 | mesh = SubResource("CylinderMesh_ar5bg") 839 | script = ExtResource("4_h60ui") 840 | target_name = "25406946714102" 841 | 842 | [node name="25406946714102" type="MeshInstance3D" parent="PathPointManager"] 843 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, -12) 844 | transparency = 0.2 845 | cast_shadow = 0 846 | mesh = SubResource("SphereMesh_mklxc") 847 | script = ExtResource("3_g5rit") 848 | next_neighbours = ["25409824819306"] 849 | previous_neighbours = ["25401088514103"] 850 | 851 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25406946714102"] 852 | transform = Transform3D(-0.707107, 0.707107, 3.09086e-08, 0, -4.37114e-08, 1, 0.707107, 0.707107, 3.09086e-08, 2, -1.23634e-07, 2) 853 | transparency = 0.2 854 | cast_shadow = 0 855 | mesh = SubResource("CylinderMesh_7oisk") 856 | script = ExtResource("4_h60ui") 857 | target_name = "25409824819306" 858 | 859 | [node name="25409824819306" type="MeshInstance3D" parent="PathPointManager"] 860 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, 0, -8) 861 | transparency = 0.2 862 | cast_shadow = 0 863 | mesh = SubResource("SphereMesh_5vury") 864 | script = ExtResource("3_g5rit") 865 | next_neighbours = ["25414312043973"] 866 | previous_neighbours = ["25406946714102"] 867 | 868 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25409824819306"] 869 | transform = Transform3D(-1, -1.50996e-07, -6.60024e-15, 0, -4.37114e-08, 1, -1.50996e-07, 1, 4.37114e-08, -1.20797e-06, -3.49691e-07, 8) 870 | transparency = 0.2 871 | cast_shadow = 0 872 | mesh = SubResource("CylinderMesh_7slvl") 873 | script = ExtResource("4_h60ui") 874 | target_name = "25414312043973" 875 | 876 | [node name="25414312043973" type="MeshInstance3D" parent="PathPointManager"] 877 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, 0, 8) 878 | transparency = 0.2 879 | cast_shadow = 0 880 | mesh = SubResource("SphereMesh_gvtmo") 881 | script = ExtResource("3_g5rit") 882 | next_neighbours = ["25417609221104"] 883 | previous_neighbours = ["25409824819306"] 884 | 885 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25414312043973"] 886 | transform = Transform3D(-0.707107, -0.707107, -3.09086e-08, 0, -4.37114e-08, 1, -0.707107, 0.707107, 3.09086e-08, -2, -1.23634e-07, 2) 887 | transparency = 0.2 888 | cast_shadow = 0 889 | mesh = SubResource("CylinderMesh_kjyso") 890 | script = ExtResource("4_h60ui") 891 | target_name = "25417609221104" 892 | 893 | [node name="25417609221104" type="MeshInstance3D" parent="PathPointManager"] 894 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 12) 895 | transparency = 0.2 896 | cast_shadow = 0 897 | mesh = SubResource("SphereMesh_pb0eo") 898 | script = ExtResource("3_g5rit") 899 | next_neighbours = ["25424094450421"] 900 | previous_neighbours = ["25414312043973"] 901 | 902 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25417609221104"] 903 | transform = Transform3D(-4.37114e-08, -1, -4.37114e-08, 0, -4.37114e-08, 1, -1, 4.37114e-08, 1.91069e-15, -14.5, -6.33815e-07, 6.33815e-07) 904 | transparency = 0.2 905 | cast_shadow = 0 906 | mesh = SubResource("CylinderMesh_prdcg") 907 | script = ExtResource("4_h60ui") 908 | target_name = "25424094450421" 909 | 910 | [node name="25424094450421" type="MeshInstance3D" parent="PathPointManager"] 911 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21, 0, 12) 912 | transparency = 0.2 913 | cast_shadow = 0 914 | mesh = SubResource("SphereMesh_vk67i") 915 | script = ExtResource("3_g5rit") 916 | next_neighbours = [] 917 | previous_neighbours = ["25417609221104"] 918 | 919 | [node name="2552990941880" type="MeshInstance3D" parent="PathPointManager"] 920 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 21) 921 | transparency = 0.2 922 | cast_shadow = 0 923 | mesh = SubResource("SphereMesh_hyeyr") 924 | script = ExtResource("3_g5rit") 925 | tag = 2 926 | next_neighbours = ["2554179848937"] 927 | previous_neighbours = [] 928 | 929 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/2552990941880"] 930 | transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, -3.27835e-07, -7.5) 931 | transparency = 0.2 932 | cast_shadow = 0 933 | mesh = SubResource("CylinderMesh_7rmrx") 934 | script = ExtResource("4_h60ui") 935 | target_name = "2554179848937" 936 | 937 | [node name="2554179848937" type="MeshInstance3D" parent="PathPointManager"] 938 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 6) 939 | transparency = 0.2 940 | cast_shadow = 0 941 | mesh = SubResource("SphereMesh_c8edd") 942 | script = ExtResource("3_g5rit") 943 | tag = 2 944 | next_neighbours = ["25547061985940"] 945 | previous_neighbours = ["2552990941880"] 946 | 947 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/2554179848937"] 948 | transform = Transform3D(0.707107, 0.707107, 3.09086e-08, 0, -4.37114e-08, 1, 0.707107, -0.707107, -3.09086e-08, 2, -1.23634e-07, -2) 949 | transparency = 0.2 950 | cast_shadow = 0 951 | mesh = SubResource("CylinderMesh_8di87") 952 | script = ExtResource("4_h60ui") 953 | target_name = "25547061985940" 954 | 955 | [node name="25547061985940" type="MeshInstance3D" parent="PathPointManager"] 956 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 0, 2) 957 | transparency = 0.2 958 | cast_shadow = 0 959 | mesh = SubResource("SphereMesh_digi8") 960 | script = ExtResource("3_g5rit") 961 | tag = 2 962 | next_neighbours = ["25550760014234"] 963 | previous_neighbours = ["2554179848937"] 964 | 965 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25547061985940"] 966 | transform = Transform3D(-4.37114e-08, 1, 4.37114e-08, 0, -4.37114e-08, 1, 1, 4.37114e-08, 1.91069e-15, 7.5, -3.27835e-07, 3.27835e-07) 967 | transparency = 0.2 968 | cast_shadow = 0 969 | mesh = SubResource("CylinderMesh_lu3in") 970 | script = ExtResource("4_h60ui") 971 | target_name = "25550760014234" 972 | 973 | [node name="25550760014234" type="MeshInstance3D" parent="PathPointManager"] 974 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21, 0, 2) 975 | transparency = 0.2 976 | cast_shadow = 0 977 | mesh = SubResource("SphereMesh_ku5xk") 978 | script = ExtResource("3_g5rit") 979 | tag = 2 980 | next_neighbours = [] 981 | previous_neighbours = ["25547061985940"] 982 | 983 | [node name="25554500152730" type="MeshInstance3D" parent="PathPointManager"] 984 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21, 0, -2) 985 | transparency = 0.2 986 | cast_shadow = 0 987 | mesh = SubResource("SphereMesh_sourp") 988 | script = ExtResource("3_g5rit") 989 | tag = 2 990 | next_neighbours = ["25557090279495"] 991 | previous_neighbours = [] 992 | 993 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25554500152730"] 994 | transform = Transform3D(-4.37114e-08, -1, -4.37114e-08, 0, -4.37114e-08, 1, -1, 4.37114e-08, 1.91069e-15, -7.5, -3.27835e-07, 3.27835e-07) 995 | transparency = 0.2 996 | cast_shadow = 0 997 | mesh = SubResource("CylinderMesh_quwpm") 998 | script = ExtResource("4_h60ui") 999 | target_name = "25557090279495" 1000 | 1001 | [node name="25557090279495" type="MeshInstance3D" parent="PathPointManager"] 1002 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 0, -2) 1003 | transparency = 0.2 1004 | cast_shadow = 0 1005 | mesh = SubResource("SphereMesh_jgccw") 1006 | script = ExtResource("3_g5rit") 1007 | tag = 2 1008 | next_neighbours = ["25559974242640"] 1009 | previous_neighbours = ["25554500152730"] 1010 | 1011 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25557090279495"] 1012 | transform = Transform3D(-0.707107, -0.707107, -3.09086e-08, 0, -4.37114e-08, 1, -0.707107, 0.707107, 3.09086e-08, -4, -2.47269e-07, 4) 1013 | transparency = 0.2 1014 | cast_shadow = 0 1015 | mesh = SubResource("CylinderMesh_gxud7") 1016 | script = ExtResource("4_h60ui") 1017 | custom_curve_interp_step_size = 0.0 1018 | target_name = "25559974242640" 1019 | 1020 | [node name="25559974242640" type="MeshInstance3D" parent="PathPointManager"] 1021 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, 6) 1022 | transparency = 0.2 1023 | cast_shadow = 0 1024 | mesh = SubResource("SphereMesh_r182t") 1025 | script = ExtResource("3_g5rit") 1026 | tag = 2 1027 | next_neighbours = ["25563333901236"] 1028 | previous_neighbours = ["25557090279495"] 1029 | 1030 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25559974242640"] 1031 | transform = Transform3D(-1, -1.50996e-07, -6.60024e-15, 0, -4.37114e-08, 1, -1.50996e-07, 1, 4.37114e-08, -1.13247e-06, -3.27835e-07, 7.5) 1032 | transparency = 0.2 1033 | cast_shadow = 0 1034 | mesh = SubResource("CylinderMesh_sgbka") 1035 | script = ExtResource("4_h60ui") 1036 | target_name = "25563333901236" 1037 | 1038 | [node name="25563333901236" type="MeshInstance3D" parent="PathPointManager"] 1039 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, 21) 1040 | transparency = 0.2 1041 | cast_shadow = 0 1042 | mesh = SubResource("SphereMesh_vjdso") 1043 | script = ExtResource("3_g5rit") 1044 | tag = 2 1045 | next_neighbours = [] 1046 | previous_neighbours = ["25559974242640"] 1047 | 1048 | [node name="25337795940120" type="MeshInstance3D" parent="PathPointManager"] 1049 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, 9) 1050 | transparency = 0.2 1051 | cast_shadow = 0 1052 | mesh = SubResource("SphereMesh_tmfmm") 1053 | script = ExtResource("3_g5rit") 1054 | tag = 1 1055 | next_neighbours = ["25381146941285"] 1056 | previous_neighbours = ["25409045403390"] 1057 | 1058 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25337795940120"] 1059 | transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, -3.93403e-07, -9) 1060 | transparency = 0.2 1061 | cast_shadow = 0 1062 | mesh = SubResource("CylinderMesh_7tjst") 1063 | script = ExtResource("4_h60ui") 1064 | target_name = "25381146941285" 1065 | 1066 | [node name="25381146941285" type="MeshInstance3D" parent="PathPointManager"] 1067 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5, 0, -9) 1068 | transparency = 0.2 1069 | cast_shadow = 0 1070 | mesh = SubResource("SphereMesh_i8wlo") 1071 | script = ExtResource("3_g5rit") 1072 | tag = 1 1073 | next_neighbours = ["25406557930403"] 1074 | previous_neighbours = ["25337795940120"] 1075 | 1076 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25381146941285"] 1077 | transform = Transform3D(-4.37114e-08, -1, -4.37114e-08, 0, -4.37114e-08, 1, -1, 4.37114e-08, 1.91069e-15, -2, -8.74228e-08, 8.74228e-08) 1078 | transparency = 0.2 1079 | cast_shadow = 0 1080 | mesh = SubResource("CylinderMesh_du4bb") 1081 | script = ExtResource("4_h60ui") 1082 | target_name = "25406557930403" 1083 | 1084 | [node name="25406557930403" type="MeshInstance3D" parent="PathPointManager"] 1085 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9, 0, -9) 1086 | transparency = 0.2 1087 | cast_shadow = 0 1088 | mesh = SubResource("SphereMesh_peiox") 1089 | script = ExtResource("3_g5rit") 1090 | tag = 1 1091 | next_neighbours = ["25409045403390"] 1092 | previous_neighbours = ["25381146941285"] 1093 | 1094 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25406557930403"] 1095 | transform = Transform3D(-1, -1.50996e-07, -6.60024e-15, 0, -4.37114e-08, 1, -1.50996e-07, 1, 4.37114e-08, -1.35896e-06, -3.93403e-07, 9) 1096 | transparency = 0.2 1097 | cast_shadow = 0 1098 | mesh = SubResource("CylinderMesh_fytsi") 1099 | script = ExtResource("4_h60ui") 1100 | target_name = "25409045403390" 1101 | 1102 | [node name="25409045403390" type="MeshInstance3D" parent="PathPointManager"] 1103 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9, 0, 9) 1104 | transparency = 0.2 1105 | cast_shadow = 0 1106 | mesh = SubResource("SphereMesh_bu173") 1107 | script = ExtResource("3_g5rit") 1108 | tag = 1 1109 | next_neighbours = ["25337795940120"] 1110 | previous_neighbours = ["25406557930403"] 1111 | 1112 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25409045403390"] 1113 | transform = Transform3D(-4.37114e-08, 1, 4.37114e-08, 0, -4.37114e-08, 1, 1, 4.37114e-08, 1.91069e-15, 2, -8.74228e-08, 8.74228e-08) 1114 | transparency = 0.2 1115 | cast_shadow = 0 1116 | mesh = SubResource("CylinderMesh_psicm") 1117 | script = ExtResource("4_h60ui") 1118 | target_name = "25337795940120" 1119 | 1120 | [node name="2597464790555" type="MeshInstance3D" parent="PathPointManager"] 1121 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21, 0, 0) 1122 | transparency = 0.2 1123 | cast_shadow = 0 1124 | mesh = SubResource("SphereMesh_p7i87") 1125 | script = ExtResource("3_g5rit") 1126 | next_neighbours = ["2597456324461", "2597460670254", "2597468901911"] 1127 | previous_neighbours = ["25167848905363"] 1128 | 1129 | [node name="PathPointer3D4" type="MeshInstance3D" parent="PathPointManager/2597464790555"] 1130 | transform = Transform3D(0.707107, 0.707107, 3.09086e-08, 0, -4.37114e-08, 1, 0.707107, -0.707107, -3.09086e-08, 3.5, -2.1636e-07, -3.5) 1131 | transparency = 0.2 1132 | cast_shadow = 0 1133 | mesh = SubResource("CylinderMesh_iwm3j") 1134 | script = ExtResource("4_h60ui") 1135 | target_name = "2597456324461" 1136 | 1137 | [node name="PathPointer3D5" type="MeshInstance3D" parent="PathPointManager/2597464790555"] 1138 | transform = Transform3D(-4.37114e-08, 1, 4.37114e-08, 0, -4.37114e-08, 1, 1, 4.37114e-08, 1.91069e-15, 3.5, -1.5299e-07, 1.5299e-07) 1139 | transparency = 0.2 1140 | cast_shadow = 0 1141 | mesh = SubResource("CylinderMesh_cdnmf") 1142 | script = ExtResource("4_h60ui") 1143 | target_name = "2597460670254" 1144 | 1145 | [node name="PathPointer3D6" type="MeshInstance3D" parent="PathPointManager/2597464790555"] 1146 | transform = Transform3D(-0.707107, 0.707107, 3.09086e-08, 0, -4.37114e-08, 1, 0.707107, 0.707107, 3.09086e-08, 3.5, -2.1636e-07, 3.5) 1147 | transparency = 0.2 1148 | cast_shadow = 0 1149 | mesh = SubResource("CylinderMesh_5oojn") 1150 | script = ExtResource("4_h60ui") 1151 | target_name = "2597468901911" 1152 | 1153 | [node name="25167848905363" type="MeshInstance3D" parent="PathPointManager"] 1154 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -28, 0, 0) 1155 | transparency = 0.2 1156 | cast_shadow = 0 1157 | mesh = SubResource("SphereMesh_jec8p") 1158 | script = ExtResource("3_g5rit") 1159 | next_neighbours = ["2597464790555"] 1160 | previous_neighbours = [] 1161 | 1162 | [node name="PathPointer3D" type="MeshInstance3D" parent="PathPointManager/25167848905363"] 1163 | transform = Transform3D(-4.37114e-08, 1, 4.37114e-08, 0, -4.37114e-08, 1, 1, 4.37114e-08, 1.91069e-15, 3.5, -1.5299e-07, 1.5299e-07) 1164 | transparency = 0.2 1165 | cast_shadow = 0 1166 | mesh = SubResource("CylinderMesh_q1lhp") 1167 | script = ExtResource("4_h60ui") 1168 | target_name = "2597464790555" 1169 | 1170 | [node name="2597460670254" type="MeshInstance3D" parent="PathPointManager"] 1171 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14, 0, 0) 1172 | transparency = 0.2 1173 | cast_shadow = 0 1174 | mesh = SubResource("SphereMesh_yj0vn") 1175 | script = ExtResource("3_g5rit") 1176 | next_neighbours = [] 1177 | previous_neighbours = ["2597464790555"] 1178 | 1179 | [node name="2597468901911" type="MeshInstance3D" parent="PathPointManager"] 1180 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14, 0, 7) 1181 | transparency = 0.2 1182 | cast_shadow = 0 1183 | mesh = SubResource("SphereMesh_kxdgj") 1184 | script = ExtResource("3_g5rit") 1185 | next_neighbours = [] 1186 | previous_neighbours = ["2597464790555"] 1187 | 1188 | [node name="2597456324461" type="MeshInstance3D" parent="PathPointManager"] 1189 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14, 0, -7) 1190 | transparency = 0.2 1191 | cast_shadow = 0 1192 | mesh = SubResource("SphereMesh_jwsji") 1193 | script = ExtResource("3_g5rit") 1194 | next_neighbours = [] 1195 | previous_neighbours = ["2597464790555"] 1196 | --------------------------------------------------------------------------------