feat(studio,stacking,animations,collections): M16 ModBison Studio, object stacking, model animations, and poly collections for MVP 2
checkpoint / checkpoint (push) Successful in 6m32s
checkpoint / checkpoint (push) Successful in 6m32s
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
class_name TransformGizmo3D
|
||||
extends Node3D
|
||||
## Interactive 3D manipulator gizmo supporting Translate, Rotate, and Scale modes with snapping.
|
||||
|
||||
signal transform_started
|
||||
signal transform_updated(mode: String, delta: Variant)
|
||||
signal transform_finished(final_pos: Vector3, final_rot: Vector3, final_scale: Vector3)
|
||||
|
||||
enum Mode { TRANSLATE, ROTATE, SCALE, NONE }
|
||||
enum Axis { NONE, X, Y, Z, XY, XZ, YZ }
|
||||
|
||||
var current_mode: Mode = Mode.TRANSLATE
|
||||
var active_axis: Axis = Axis.NONE
|
||||
var is_dragging: bool = false
|
||||
|
||||
# Snapping configuration
|
||||
var snap_enabled: bool = true
|
||||
var snap_translate: float = 1.0
|
||||
var snap_rotate_deg: float = 15.0
|
||||
var snap_scale: float = 0.5
|
||||
|
||||
var target_node: Node3D = null
|
||||
var _drag_start_pos: Vector3 = Vector3.ZERO
|
||||
var _drag_start_rot: Vector3 = Vector3.ZERO
|
||||
var _drag_start_scale: Vector3 = Vector3.ONE
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_build_gizmo_visuals()
|
||||
|
||||
|
||||
func attach_to_node(node: Node3D) -> void:
|
||||
target_node = node
|
||||
if target_node != null:
|
||||
visible = true
|
||||
global_position = target_node.global_position
|
||||
global_rotation = target_node.global_rotation
|
||||
else:
|
||||
visible = false
|
||||
|
||||
|
||||
func set_mode(new_mode: Mode) -> void:
|
||||
current_mode = new_mode
|
||||
_update_visual_mode()
|
||||
|
||||
|
||||
func set_snap(enabled: bool, translate_step: float = 1.0, rotate_step: float = 15.0) -> void:
|
||||
snap_enabled = enabled
|
||||
snap_translate = translate_step
|
||||
snap_rotate_deg = rotate_step
|
||||
|
||||
|
||||
func _build_gizmo_visuals() -> void:
|
||||
# Build colored 3D axis handles for X (Red), Y (Green), Z (Blue)
|
||||
var x_mat := StandardMaterial3D.new()
|
||||
x_mat.albedo_color = Color(0.9, 0.2, 0.2)
|
||||
x_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
|
||||
var y_mat := StandardMaterial3D.new()
|
||||
y_mat.albedo_color = Color(0.2, 0.85, 0.2)
|
||||
y_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
|
||||
var z_mat := StandardMaterial3D.new()
|
||||
z_mat.albedo_color = Color(0.2, 0.4, 0.95)
|
||||
z_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
|
||||
# X axis line + arrow
|
||||
var x_axis := _create_axis_cylinder(Vector3(1, 0, 0), x_mat)
|
||||
x_axis.name = "AxisX"
|
||||
add_child(x_axis)
|
||||
|
||||
# Y axis line + arrow
|
||||
var y_axis := _create_axis_cylinder(Vector3(0, 1, 0), y_mat)
|
||||
y_axis.name = "AxisY"
|
||||
add_child(y_axis)
|
||||
|
||||
# Z axis line + arrow
|
||||
var z_axis := _create_axis_cylinder(Vector3(0, 0, 1), z_mat)
|
||||
z_axis.name = "AxisZ"
|
||||
add_child(z_axis)
|
||||
|
||||
|
||||
func _create_axis_cylinder(dir: Vector3, mat: Material) -> Node3D:
|
||||
var container := Node3D.new()
|
||||
var shaft := MeshInstance3D.new()
|
||||
var cyl := CylinderMesh.new()
|
||||
cyl.top_radius = 0.03
|
||||
cyl.bottom_radius = 0.03
|
||||
cyl.height = 1.5
|
||||
cyl.material = mat
|
||||
shaft.mesh = cyl
|
||||
|
||||
# Point cylinder along direction
|
||||
if dir == Vector3(1, 0, 0):
|
||||
shaft.rotation_degrees = Vector3(0, 0, -90)
|
||||
shaft.position = Vector3(0.75, 0, 0)
|
||||
elif dir == Vector3(0, 1, 0):
|
||||
shaft.position = Vector3(0, 0.75, 0)
|
||||
elif dir == Vector3(0, 0, 1):
|
||||
shaft.rotation_degrees = Vector3(90, 0, 0)
|
||||
shaft.position = Vector3(0, 0, 0.75)
|
||||
|
||||
container.add_child(shaft)
|
||||
return container
|
||||
|
||||
|
||||
func _update_visual_mode() -> void:
|
||||
visible = (target_node != null and current_mode != Mode.NONE)
|
||||
|
||||
|
||||
func start_drag(axis: Axis) -> void:
|
||||
if target_node == null:
|
||||
return
|
||||
active_axis = axis
|
||||
is_dragging = true
|
||||
_drag_start_pos = target_node.position
|
||||
_drag_start_rot = target_node.rotation_degrees
|
||||
_drag_start_scale = target_node.scale
|
||||
transform_started.emit()
|
||||
|
||||
|
||||
func apply_drag_delta(delta_vec: Vector3) -> void:
|
||||
if not is_dragging or target_node == null:
|
||||
return
|
||||
|
||||
match current_mode:
|
||||
Mode.TRANSLATE:
|
||||
var new_pos := target_node.position + delta_vec
|
||||
if snap_enabled and snap_translate > 0.0:
|
||||
new_pos.x = snappedf(new_pos.x, snap_translate)
|
||||
new_pos.y = snappedf(new_pos.y, snap_translate)
|
||||
new_pos.z = snappedf(new_pos.z, snap_translate)
|
||||
target_node.position = new_pos
|
||||
global_position = target_node.global_position
|
||||
transform_updated.emit("translate", new_pos)
|
||||
|
||||
Mode.ROTATE:
|
||||
var rot := target_node.rotation_degrees + delta_vec * 10.0
|
||||
if snap_enabled and snap_rotate_deg > 0.0:
|
||||
rot.x = snappedf(rot.x, snap_rotate_deg)
|
||||
rot.y = snappedf(rot.y, snap_rotate_deg)
|
||||
rot.z = snappedf(rot.z, snap_rotate_deg)
|
||||
target_node.rotation_degrees = rot
|
||||
transform_updated.emit("rotate", rot)
|
||||
|
||||
Mode.SCALE:
|
||||
var sc := target_node.scale + delta_vec
|
||||
sc.x = maxf(0.1, sc.x)
|
||||
sc.y = maxf(0.1, sc.y)
|
||||
sc.z = maxf(0.1, sc.z)
|
||||
if snap_enabled and snap_scale > 0.0:
|
||||
sc.x = snappedf(sc.x, snap_scale)
|
||||
sc.y = snappedf(sc.y, snap_scale)
|
||||
sc.z = snappedf(sc.z, snap_scale)
|
||||
target_node.scale = sc
|
||||
transform_updated.emit("scale", sc)
|
||||
|
||||
|
||||
func finish_drag() -> void:
|
||||
if not is_dragging:
|
||||
return
|
||||
is_dragging = false
|
||||
active_axis = Axis.NONE
|
||||
if target_node != null:
|
||||
transform_finished.emit(target_node.position, target_node.rotation_degrees, target_node.scale)
|
||||
Reference in New Issue
Block a user