Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions VERSIONS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 4.4.0
- **minor-breakage** Add gizmos to help align interactable hinges, sliders, and joysticks

# 4.3.0
- Upgraded project to Godot 4.1 as the new minimum version.
- Added reporting of stage load errors.
Expand Down
172 changes: 172 additions & 0 deletions addons/godot-xr-tools/editor/gizmos/hinge_origin.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
extends EditorNode3DGizmoPlugin


## Editor Gizmo for [XRToolsInteractableHingeOrigin]
##
## This editor gizmo helps align interactable hinge joints.


var undo_redo : EditorUndoRedoManager


func _init() -> void:
create_material("axis", Color(1, 1, 0))
create_material("extent", Color(1, 1, 0, 0.3), false, true)
create_handle_material("handles")


func _get_gizmo_name() -> String:
return "InteractableHingeOrigin"


func _get_handle_name(
_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool) -> String:
# Return minimum or maximum handle name
return "Minimum" if p_handle_id == 0 else "Maximum"


func _get_handle_value(
p_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool) -> Variant:
# Return limit
return _get_limit(p_gizmo.get_node_3d(), p_handle_id)


func _set_handle(
p_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool,
p_camera : Camera3D,
p_screen_pos : Vector2) -> void:
# Get the hinge origin node
var origin : XRToolsInteractableHingeOrigin = p_gizmo.get_node_3d()
var origin_pos := origin.global_position
var origin_dir := origin.global_transform.basis.x

# Construct the plane
var plane := Plane(origin_dir, origin_pos)

# Find the intersection between the ray and the plane
var v_intersect = plane.intersects_ray(
p_camera.global_position,
p_camera.project_ray_normal(p_screen_pos))
if not v_intersect:
return

# Find the local position and the delta in angle
var local := origin.to_local(v_intersect)
var old_local := _get_limit_pos(origin, p_handle_id)
var delta := rad_to_deg(old_local.signed_angle_to(local, Vector3.LEFT))

# Adjust the current limit
var limit := _get_limit(origin, p_handle_id)
limit = snappedf(limit + delta, 5)
_set_limit(origin, p_handle_id, limit)


func _commit_handle(
p_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool,
p_restore : Variant,
p_cancel : bool) -> void:
# Get the slider origin node
var origin : XRToolsInteractableHingeOrigin = p_gizmo.get_node_3d()

# If canceling then restore limit
if p_cancel:
_set_limit(origin, p_handle_id, p_restore)
return

# Commit the handle change
match p_handle_id:
0:
undo_redo.create_action("Set interactable hinge limit_minimum")
undo_redo.add_do_method(origin, "set_limit_minimum", origin.limit_minimum)
undo_redo.add_undo_method(origin, "set_limit_minimum", p_restore)
undo_redo.commit_action()
1:
undo_redo.create_action("Set interactable hinge limit_maximum")
undo_redo.add_do_method(origin, "set_limit_maximum", origin.limit_maximum)
undo_redo.add_undo_method(origin, "set_limit_maximum", p_restore)
undo_redo.commit_action()


func _has_gizmo(p_node : Node3D) -> bool:
return p_node is XRToolsInteractableHingeOrigin


func _redraw(p_gizmo : EditorNode3DGizmo) -> void:
# Clear the current gizmo contents
p_gizmo.clear()

# Get the hinge origin and its extents
var origin : XRToolsInteractableHingeOrigin = p_gizmo.get_node_3d()
var min_angle := deg_to_rad(origin.limit_minimum)
var max_angle := deg_to_rad(origin.limit_maximum)

# Draw the lines (for the axis)
var lines := PackedVector3Array()
lines.push_back(Vector3(-0.2, 0, 0))
lines.push_back(Vector3(0.2, 0, 0))
p_gizmo.add_lines(lines, get_material("axis", p_gizmo))

# Construct an immediate mesh for the extent
var mesh := ImmediateMesh.new()
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
var steps := int(abs(max_angle - min_angle) / 0.1)
for i in steps + 1:
if i != 0:
mesh.surface_add_vertex(Vector3.ZERO)
var angle := lerpf(min_angle, max_angle, i / float(steps))
mesh.surface_add_vertex(
Vector3(0, sin(angle) * 0.2, cos(angle) * 0.2))
mesh.surface_end()

# Draw the extent mesh
p_gizmo.add_mesh(mesh, get_material("extent", p_gizmo))

# Add the handles
var handles := PackedVector3Array()
handles.push_back(_get_limit_pos(origin, 0))
handles.push_back(_get_limit_pos(origin, 1))
p_gizmo.add_handles(handles, get_material("handles", p_gizmo), [])


# Get the limit of a hinge by handle
func _get_limit(
p_origin : XRToolsInteractableHingeOrigin,
p_handle_id : int) -> float:
# Read the limit
match p_handle_id:
0:
return p_origin.limit_minimum
1:
return p_origin.limit_maximum
_:
return 0.0


# Get the limit position of a slider by handle
func _get_limit_pos(
p_origin : XRToolsInteractableHingeOrigin,
p_handle_id : int) -> Vector3:
# Return the limit position
var angle := deg_to_rad(_get_limit(p_origin, p_handle_id))
return Vector3(0, sin(angle) * 0.2, cos(angle) * 0.2)


# Set the limit of a hinge by handle
func _set_limit(
p_origin : XRToolsInteractableHingeOrigin,
p_handle_id : int,
p_limit : float) -> void:
# Apply the limit
match p_handle_id:
0:
p_origin.limit_minimum = p_limit
1:
p_origin.limit_maximum = p_limit
212 changes: 212 additions & 0 deletions addons/godot-xr-tools/editor/gizmos/joystick_origin.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
extends EditorNode3DGizmoPlugin


## Editor Gizmo for [XRToolsInteractableJoystickOrigin]
##
## This editor gizmo helps align interactable joystick joints.


var undo_redo : EditorUndoRedoManager


func _init() -> void:
create_material("extent", Color(1, 1, 0, 0.3), false, true)
create_handle_material("handles")


func _get_gizmo_name() -> String:
return "InteractableJoystickOrigin"


func _get_handle_name(
_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool) -> String:
# Return handle name
if p_handle_id == 0: return "Minimum X"
if p_handle_id == 1: return "Maximum X"
if p_handle_id == 2: return "Minimum Y"
return "Maximum Y"


func _get_handle_value(
p_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool) -> Variant:
# Return limit
return _get_limit(p_gizmo.get_node_3d(), p_handle_id)


func _set_handle(
p_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool,
p_camera : Camera3D,
p_screen_pos : Vector2) -> void:
# Get the hinge origin node
var origin : XRToolsInteractableJoystickOrigin = p_gizmo.get_node_3d()
var origin_pos := origin.global_position
var origin_dir_x := origin.global_transform.basis.y
var origin_dir_y := -origin.global_transform.basis.x

# Construct the plane
var plane : Plane
if p_handle_id < 2:
plane = Plane(origin_dir_x, origin_pos)
else:
plane = Plane(origin_dir_y, origin_pos)

# Find the intersection between the ray and the plane
var v_intersect = plane.intersects_ray(
p_camera.global_position,
p_camera.project_ray_normal(p_screen_pos))
if not v_intersect:
return

# Find the local position
var local := origin.to_local(v_intersect)
var old_local := _get_limit_pos(origin, p_handle_id)
var delta : float
if p_handle_id < 2:
delta = rad_to_deg(old_local.signed_angle_to(local, Vector3.UP))
else:
delta = rad_to_deg(old_local.signed_angle_to(local, Vector3.LEFT))

# Adjust the current limit
var limit := _get_limit(origin, p_handle_id)
limit = snappedf(limit + delta, 5)
_set_limit(origin, p_handle_id, limit)


func _commit_handle(
p_gizmo : EditorNode3DGizmo,
p_handle_id : int,
_secondary : bool,
p_restore : Variant,
p_cancel : bool) -> void:
# Get the slider origin node
var origin : XRToolsInteractableJoystickOrigin = p_gizmo.get_node_3d()

# If canceling then restore limit
if p_cancel:
_set_limit(origin, p_handle_id, p_restore)
return

# Commit the handle change
match p_handle_id:
0:
undo_redo.create_action("Set interactable joystick limit_x_minimum")
undo_redo.add_do_method(origin, "set_limit_x_minimum", origin.limit_x_minimum)
undo_redo.add_undo_method(origin, "set_limit_x_minimum", p_restore)
undo_redo.commit_action()
1:
undo_redo.create_action("Set interactable joystick limit_x_maximum")
undo_redo.add_do_method(origin, "set_limit_x_maximum", origin.limit_x_maximum)
undo_redo.add_undo_method(origin, "set_limit_x_maximum", p_restore)
undo_redo.commit_action()
2:
undo_redo.create_action("Set interactable joystick limit_y_minimum")
undo_redo.add_do_method(origin, "set_limit_y_minimum", origin.limit_y_minimum)
undo_redo.add_undo_method(origin, "set_limit_y_minimum", p_restore)
undo_redo.commit_action()
3:
undo_redo.create_action("Set interactable joystick limit_y_maximum")
undo_redo.add_do_method(origin, "set_limit_y_maximum", origin.limit_y_maximum)
undo_redo.add_undo_method(origin, "set_limit_y_maximum", p_restore)
undo_redo.commit_action()


func _has_gizmo(p_node : Node3D) -> bool:
return p_node is XRToolsInteractableJoystickOrigin


func _redraw(p_gizmo : EditorNode3DGizmo) -> void:
# Clear the current gizmo contents
p_gizmo.clear()

# Get the joystick origin and its extents
var origin : XRToolsInteractableJoystickOrigin = p_gizmo.get_node_3d()
var min_x_angle := deg_to_rad(origin.limit_x_minimum)
var max_x_angle := deg_to_rad(origin.limit_x_maximum)
var min_y_angle := deg_to_rad(origin.limit_y_minimum)
var max_y_angle := deg_to_rad(origin.limit_y_maximum)

# Construct an immediate mesh for the extent
var mesh := ImmediateMesh.new()
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
for i in 33:
if i != 0:
mesh.surface_add_vertex(Vector3.ZERO)
var angle := lerpf(min_x_angle, max_x_angle, i / 32.0)
mesh.surface_add_vertex(
Vector3(sin(angle) * 0.2, 0, cos(angle) * 0.2))
mesh.surface_end()
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
for i in 33:
if i != 0:
mesh.surface_add_vertex(Vector3.ZERO)
var angle := lerpf(min_y_angle, max_y_angle, i / 32.0)
mesh.surface_add_vertex(
Vector3(0, sin(angle) * 0.2, cos(angle) * 0.2))
mesh.surface_end()

# Draw the extent mesh
p_gizmo.add_mesh(mesh, get_material("extent", p_gizmo))

# Add the handles
var handles := PackedVector3Array()
handles.push_back(_get_limit_pos(origin, 0))
handles.push_back(_get_limit_pos(origin, 1))
handles.push_back(_get_limit_pos(origin, 2))
handles.push_back(_get_limit_pos(origin, 3))
p_gizmo.add_handles(handles, get_material("handles", p_gizmo), [])


# Get the limit of a joystick by handle
func _get_limit(
p_origin : XRToolsInteractableJoystickOrigin,
p_handle_id : int) -> float:
# Read the limit
match p_handle_id:
0:
return p_origin.limit_x_minimum
1:
return p_origin.limit_x_maximum
2:
return p_origin.limit_y_minimum
3:
return p_origin.limit_y_maximum
_:
return 0.0


# Get the limit position of a slider by handle
func _get_limit_pos(
p_origin : XRToolsInteractableJoystickOrigin,
p_handle_id : int) -> Vector3:
# Return the limit position
var angle := deg_to_rad(_get_limit(p_origin, p_handle_id))
match p_handle_id:
0, 1:
return Vector3(sin(angle) * 0.2, 0, cos(angle) * 0.2)
2, 3:
return Vector3(0, sin(angle) * 0.2, cos(angle) * 0.2)
_:
return Vector3.ZERO


# Set the limit of a joystick by handle
func _set_limit(
p_origin : XRToolsInteractableJoystickOrigin,
p_handle_id : int,
p_limit : float) -> void:
# Apply the limit
match p_handle_id:
0:
p_origin.limit_x_minimum = p_limit
1:
p_origin.limit_x_maximum = p_limit
2:
p_origin.limit_y_minimum = p_limit
3:
p_origin.limit_y_maximum = p_limit
Loading