Skip to content

Instantly share code, notes, and snippets.

@HungryProton
Last active April 20, 2022 05:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HungryProton/b67fefe76082cb04cfc48842fb1aa270 to your computer and use it in GitHub Desktop.
Save HungryProton/b67fefe76082cb04cfc48842fb1aa270 to your computer and use it in GitHub Desktop.
Spatial gizmo example - A box you can resize
tool
class_name ConceptBoxInput
extends Spatial
signal input_changed
signal property_changed
export var size := Vector3.ONE setget set_size
var auto_center := false setget set_auto_center # TODO: implement standard AABB behavior
export var center := Vector3.ZERO setget set_center
func _ready() -> void:
add_user_signal('input_changed')
set_notify_local_transform(true)
func _get_property_list() -> Array:
#if not auto_center:
# return [{"name": "Center", "type": TYPE_VECTOR3}]
return []
func _notification(type: int):
if type == NOTIFICATION_TRANSFORM_CHANGED:
_on_box_changed()
func is_inside(pos: Vector3, ignore_y_axis: bool = false) -> bool:
var t = transform
if is_inside_tree():
t = global_transform
var local = t.xform_inv(pos)
if ignore_y_axis:
local.y = size.y / 3.0
var aabb = AABB(center - (size / 2.0), size)
return aabb.has_point(local)
func set_size(val: Vector3) -> void:
size = val
_on_box_changed()
func set_auto_center(val: bool) -> void:
auto_center = val
property_list_changed_notify()
func set_center(val: Vector3) -> void:
center = val
property_list_changed_notify()
_on_box_changed()
func _on_box_changed() -> void:
emit_signal("input_changed", self) # That tell the ConceptGraph to rerun the simulation
emit_signal("property_changed")
tool
extends EditorPlugin
var _editor_gizmo_plugins: Array
func _enter_tree() -> void:
_register_editor_gizmos()
func _exit_tree() -> void:
_deregister_editor_gizmos()
func _register_editor_gizmos() -> void:
if not _editor_gizmo_plugins:
_editor_gizmo_plugins = []
if _editor_gizmo_plugins.size() > 0:
_deregister_editor_gizmos()
var box_gizmo = preload("src/editor/gizmos/box_gizmo_plugin.gd").new()
box_gizmo.editor_plugin = self
_editor_gizmo_plugins.append(box_gizmo)
add_spatial_gizmo_plugin(box_gizmo)
func _deregister_editor_gizmos() -> void:
if _editor_gizmo_plugins:
for gizmo in _editor_gizmo_plugins:
remove_spatial_gizmo_plugin(gizmo)
_editor_gizmo_plugins = []
extends EditorSpatialGizmoPlugin
var editor_plugin: EditorPlugin
var _previous_size
func _init():
create_material("lines", Color(1, 1, 1))
create_material("box", Color(1.0, 1.0, 1.0, 0.1))
create_handle_material("handles")
func get_name() -> String:
return "ConceptBoxInput"
func has_gizmo(node):
return node is ConceptBoxInput
func get_handle_name(gizmo: EditorSpatialGizmo, index: int) -> String:
return "Handle " + String(index)
func get_handle_value(gizmo: EditorSpatialGizmo, index: int):
var box = gizmo.get_spatial_node()
return box.size
"""
Automatically called when a handle is moved around.
"""
func set_handle(gizmo: EditorSpatialGizmo, index: int, camera: Camera, point: Vector2) -> void:
var box: ConceptBoxInput = gizmo.get_spatial_node()
if not _previous_size:
_previous_size = box.size
var global_transform: Transform = box.transform
if box.is_inside_tree():
global_transform = box.get_global_transform()
var global_inverse: Transform = global_transform.affine_inverse()
var ray_from = camera.project_ray_origin(point)
var ray_dir = camera.project_ray_normal(point)
if index < 6:
# Face handle
var i := index % 3
var axis: Vector3
axis[i] = 1.0
var p1 = axis * 4096
var p2 = -p1
var g1 = global_inverse.xform(ray_from)
var g2 = global_inverse.xform(ray_from + ray_dir * 4096)
var points = Geometry.get_closest_points_between_segments(p1, p2, g1, g2)
var d = points[0][i]
# TODO : Add snap support (when holding ctrl)
# TODO : Add symetric edition (when holding shift)
if index > 2:
d *= -1.0
box.size[i] = d + (_previous_size[i] / 2.0)
box.center[i] = (box.size[i] * 0.5) - d
if index < 3:
box.center[i] *= -1.0
redraw(gizmo)
else:
# Corner handle
print("Not implemented yet")
box.property_list_changed_notify()
"""
Handle Undo / Redo after a handle was moved.
"""
func commit_handle(gizmo: EditorSpatialGizmo, index: int, restore, cancel: bool = false) -> void:
var box = gizmo.get_spatial_node()
var ur = editor_plugin.get_undo_redo()
ur.create_action("Resize Box Input")
ur.add_do_method(box, "translate", box.center)
ur.add_do_property(box, "center", Vector3.ZERO)
ur.add_do_property(box, "size", box.size)
ur.add_undo_method(box, "translate", -box.center)
ur.add_undo_property(box, "size", restore)
ur.add_undo_method(box, "property_list_changed_notify") # TMP hack, find why the inspector does not refresh automatically on undo
ur.commit_action()
_previous_size = null
redraw(gizmo)
func redraw(gizmo: EditorSpatialGizmo):
gizmo.clear()
var box := gizmo.get_spatial_node() as ConceptBoxInput
# TMP: force the gizmo to redraw everytime we change a parameter. This should probably
# happen automatically but for some reason, it doesn't
if not box.is_connected("property_changed", self, "redraw"):
box.connect("property_changed", self, "redraw", [gizmo])
var lines := _get_box_lines(box)
var handles := _get_box_handles(box)
gizmo.add_handles(handles, get_material("handles", gizmo))
gizmo.add_lines(lines, get_material("lines", gizmo))
gizmo.add_collision_segments(lines)
"""
Returns all the points that make up the box lines.
"""
func _get_box_lines(box: ConceptBoxInput) -> PoolVector3Array:
var lines = PoolVector3Array()
var c = _get_box_corners(box)
lines.push_back(c[0])
lines.push_back(c[1])
lines.push_back(c[1])
lines.push_back(c[2])
lines.push_back(c[2])
lines.push_back(c[3])
lines.push_back(c[3])
lines.push_back(c[0])
lines.push_back(c[0])
lines.push_back(c[5])
lines.push_back(c[1])
lines.push_back(c[6])
lines.push_back(c[2])
lines.push_back(c[7])
lines.push_back(c[3])
lines.push_back(c[4])
lines.push_back(c[4])
lines.push_back(c[5])
lines.push_back(c[5])
lines.push_back(c[6])
lines.push_back(c[6])
lines.push_back(c[7])
lines.push_back(c[7])
lines.push_back(c[4])
return lines
"""
Returns the position of each handles. One on each corner, one at the center of each faces.
"""
func _get_box_handles(box: ConceptBoxInput) -> PoolVector3Array:
var handles := PoolVector3Array()
var hs := box.size / 2.0
# Ordered on purpose so their (index % 3) matches the associated Vector3 axis
handles.append(Vector3(hs.x, 0.0, 0.0) + box.center)
handles.append(Vector3(0.0, hs.y, 0.0) + box.center)
handles.append(Vector3(0.0, 0.0, hs.z) + box.center)
handles.append(Vector3(-hs.x, 0.0, 0.0) + box.center)
handles.append(Vector3(0.0, -hs.y, 0.0) + box.center)
handles.append(Vector3(0.0, 0.0, -hs.z) + box.center)
# Append _get_box_corners(box) there once we figure out the math to move corners
return handles
"""
Calculate the position of each corners of the box.
"""
func _get_box_corners(box: ConceptBoxInput) -> PoolVector3Array:
var corners = PoolVector3Array()
var hs := box.size / 2.0
var left := -hs.x
var right := hs.x
var top := hs.y
var bottom := -hs.y
var front := hs.z
var back := - hs.z
corners.append(Vector3(left, top, front) + box.center)
corners.append(Vector3(right, top, front) + box.center)
corners.append(Vector3(right, bottom, front) + box.center)
corners.append(Vector3(left, bottom, front) + box.center)
corners.append(Vector3(left, bottom, back) + box.center)
corners.append(Vector3(left, top, back) + box.center)
corners.append(Vector3(right, top, back) + box.center)
corners.append(Vector3(right, bottom, back) + box.center)
return corners
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment