I am attempting to recreate the old drag / move tool from Roblox in Godot, however one of the main problems is that Nodes dragged against 2 surfaces will intersect those 2 surfaces. I tried to solve this by adding the nodes AABB to the position calculation, but it is still (albeit to a lesser extent) a problem:
var aabb = _calculate_spatial_bounds(saved_part, false)
var targ_pos = get_collision_point()
var norm = get_collision_normal()
var rounded_pos = snappedvec3(targ_pos, 0.25)
saved_part.position = (rounded_pos - offset) + norm * (aabb.size / 2)
I have learned that most attempts at recreating the drag tool in Roblox often use a function called “Model:MoveTo(vector3)”, which tries to perform a ‘safe move’ on the Model (collection of objects), which is where it tries to move the Model to the given position, and if there is an intersection, it moves it upward to avoid intersecting any other Parts. Is there an equivalent function or simple method of performing a ‘safe move’ in Godot?
Here’s the full program in case it helps or you wanna use it (most of the “welding” portions don’t work)
extends RayCast3D
var text
var offset = Vector3(0, 0, 0)
var collider
var saved_part
var dragged_to_part
var can_grab = false
var drag_count = 0 # if print(get_collider()) zero, then change can_grab but like otherwise you are already grabbing a part
var rot_count_x = 0
var rot_count_y = 0
var rot_count_z = 0
var weld_count = 0
var part_rotation = Vector3(0, 0, 0)
var pos_diff = Vector3.ZERO
var saved_part_name = ""
func _physics_process(_delta):
#if button_toggle.button_toggle: # debug
var par = get_parent()
var play = par.get_parent()
collider = get_collider()
#print(saved_part)
if collider:
if not collider.name == "Floor": # exclude floor
saved_part_name = collider.name
#play.get_child(4).text = saved_part_name # debug
#print(get_collision_normal().inverse()) # debug
if drag_count == 0:
saved_part = collider
add_exception(collider)
can_grab = true
drag_count += 1
#if not collider or collider and collider.name == "Floor": # debug
#play.get_child(4).text = saved_part_name # debug
if can_grab and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
weld_count = 0
#if button_toggle.button_toggle: # debug
if saved_part.find_child("CollisionShape3D"):
saved_part.find_child("CollisionShape3D").disabled = true
var targ_pos = get_collision_point()
var norm = get_collision_normal()
if saved_part is RigidBody3D:
saved_part.freeze = true
var aabb = _calculate_spatial_bounds(saved_part, false)
var rounded_pos = snappedvec3(targ_pos, 0.25)
if can_grab:
force_raycast_update()
if get_collider():
dragged_to_part = get_collider()
#print("Ground: ", dragged_to_part) # debug
#print("dragged: ", saved_part)saved_part # debug
saved_part.position = (rounded_pos - offset) + norm * (aabb.size / 2)
if Input.is_key_pressed(KEY_R) and rot_count_x == 0:
saved_part.rotate(Vector3(0, 1, 0), PI / 2)
rot_count_x += 1
if Input.is_key_pressed(KEY_T) and rot_count_z == 0:
saved_part.rotate(Vector3(1, 0, 0), PI / 2)
rot_count_z += 1
if not Input.is_key_pressed(KEY_R):
rot_count_x = 0
if not Input.is_key_pressed(KEY_T):
rot_count_z = 0
if saved_part.get_node("Area3D"): # welding related stuff
for body in saved_part.get_node("Area3D").get_overlapping_bodies():
if body is HingeJoint3D:
print(body.node_a)
body.queue_free()
if can_grab and Input.is_action_just_pressed("click"):
var offset = get_collision_point() - saved_part.position
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
if can_grab:
can_grab = false
dragged_to_part = null
if saved_part is RigidBody3D:
saved_part.freeze = false
if saved_part:
if saved_part.find_child("CollisionShape3D"):
saved_part.find_child("CollisionShape3D").disabled = false
drag_count = 0
remove_exception(saved_part)
if dragged_to_part:
if dragged_to_part.get_node("HingeJoint3D"):
print("has weld") # debug
if saved_part.get_node("Area3D") and weld_count == 0: # welding stuff
for body in saved_part.get_node("Area3D").get_overlapping_bodies():
if not body == saved_part:
var weld = HingeJoint3D.new()
weld.set_flag(HingeJoint3D.FLAG_USE_LIMIT, true)
weld.set_param(HingeJoint3D.PARAM_BIAS, 0.99)
weld.set_param(HingeJoint3D.PARAM_LIMIT_LOWER, 0)
weld.set_param(HingeJoint3D.PARAM_LIMIT_UPPER, 0)
weld.set_node_a(saved_part.get_path())
weld.set_node_b(body.get_path())
saved_part.add_child(weld)
weld_count = 1
print("New weld created between the touching objects " + saved_part.name + " and " + body.name)
func _calculate_spatial_bounds(parent : Node3D, exclude_top_level_transform: bool) -> AABB: # taken from reddit
var bounds : AABB = AABB()
if parent is VisualInstance3D:
bounds = parent.get_aabb();
for i in range(parent.get_child_count()):
var child : Node3D = parent.get_child(i)
if child:
var child_bounds : AABB = _calculate_spatial_bounds(child, false)
if bounds.size == Vector3.ZERO && parent:
bounds = child_bounds
else:
bounds = bounds.merge(child_bounds)
if bounds.size == Vector3.ZERO && !parent:
bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4))
if !exclude_top_level_transform:
bounds = parent.transform * bounds
return bounds
func snappedvec3(vec3: Vector3, snap_rate: float):
return Vector3(snapped(vec3.x, snap_rate), snapped(vec3.y, snap_rate), snapped(vec3.z, snap_rate))
In Roblox, you use models and primary parts to move a myriad of descendant baseparts around while keeping relative positions.
In Godot, the position property is relative to the parent node, not the entire world coordinates. That being said, you just need to modify the position of the top-level node (that you would consider your model)
As said, editing the position is relative to the parent’s one, meaning if you want your model to be in 0, 0, 0, it wouldn’t be centered in world coordinates, it would be centered to the parent instead (which may lead to 0,0,0 in particular scenarios but that’s something else)
Fortunately, you can manipulate the global_position and use that to express your world coords !
Basically, nodes work like attachments in Roblox
Position <=> position
WorldPosition <=> global_position
There’s no generalized built in functionality like that in Godot. You could implement is relatively simply but for specific suggestions you’ll need to describe the constraints of the system, or post a video that shows how it works in Roblox.
I think I could probably do something to mimic this behavior using either variable size ShapeCasts, or by just having the object be moved with a high force, as I plan to use RigidBody3D nodes for this, as opposed to directly setting the position. A way to directly set the position to a known area that won’t intersect would be ideal, as it would avoid issues with Area3D nodes not detecting the object, despite the object being hovered over them.
I think both of these options would work well, especially move_and_collide() as it emits data when it collides, meaning I can store the last position and keep it there until it isn’t colliding anymore, and this seems compatible with my idea of making a prototype vehicle builder.
One possible problem is that I plan to have the ability to attach objects onto other off grid objects (for example, a vehicle), and Roblox seems to handle that by adding the position and rotation of the object being dragged against to the grid, as shown in this video
One other problem is that I don’t know how to detect if a part is going to intersect with another part without already knowing the position of all of the other parts, which I’m pretty sure you also need to do if you use move_and_collide() as you need to know when the part isn’t intersecting, so that way the object can be moved, and then check again for intersecting parts.