-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathEasyIKSkeleton.gd
193 lines (145 loc) · 6.33 KB
/
EasyIKSkeleton.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
extends Node3D
# FABRIK for Skeletons in Godot4 based on https://github.com/joaen/EasyIK/blob/master/EasyIK/Assets/Scripts/EasyIK.cs
# Usage:
# - add the script to a node
# call the activate function
# fabrik.activate(bone_ids, skeleton, left_hand_target, left_hand_pole)
# bone_ids is an Array with the bone ids of the chain
# skeleton is the skeleton
# left_hand_target is a Node3D
# left_hand_pole is another Node3D
var numberOfJoints := 2
var iterations := 10
var tolerance := 0.005
var jointTransforms := []
var jointPositions := []
var boneLength := []
var jointStartDirection := []
var startRotation := []
var bones := []
var bone_ids := []
var ikTarget:Transform3D
var distanceToTarget:float
var jointChainLength:float
var startPosition:Vector3
var poleTarget:Transform3D
var skeleton:Skeleton3D
var ikTargetNode:Node3D
var poleTargetNode:Node3D
var active := false
func _ready():
jointTransforms.resize(numberOfJoints+1)
jointPositions.resize(numberOfJoints+1)
jointStartDirection.resize(numberOfJoints+1)
startRotation.resize(numberOfJoints+1)
func activate(_bone_ids:Array, _skeleton:Skeleton3D, _ikTargetNode:Node3D, _poleTargetNode:Node3D):
bone_ids = _bone_ids
skeleton = _skeleton
ikTargetNode = _ikTargetNode
poleTargetNode = _poleTargetNode
active = true
for b in bone_ids:
bones.append(skeleton.get_bone_global_pose(b))
jointChainLength = 0
for i in range(numberOfJoints):
var bl = bones[i].origin.distance_to(bones[i+1].origin)
boneLength.append(bl)
jointChainLength += bl
jointStartDirection[i] = bones[i+1].origin - bones[i].origin
startRotation[i] = bones[i].basis.get_rotation_quaternion()
#startRotation[i] = skeleton.get_bone_pose_rotation(bone_ids[i])
func _physics_process(_delta):
if !active:
return
fabrik_step()
func fabrik_step():
for b in bone_ids:
bones.append(skeleton.get_bone_global_pose(b))
for i in range(numberOfJoints+1):
jointTransforms[i] = bones[i]
ikTarget = ikTargetNode.global_transform
poleTarget = poleTargetNode.global_transform
SolveIK()
func SolveIK():
for i in range(jointTransforms.size()):
jointPositions[i] = jointTransforms[i].origin
distanceToTarget = jointPositions[0].distance_to(ikTarget.origin)
if distanceToTarget > jointChainLength:
var direction = ikTarget.origin - jointPositions[0]
for i in range(1, jointPositions.size()):
jointPositions[i] = jointPositions[i - 1] + direction.normalized() * boneLength[i - 1]
else:
var distToTarget:float = jointPositions[jointPositions.size() - 1].distance_to(ikTarget.origin)
var counter := 0
while distToTarget > tolerance:
startPosition = jointPositions[0]
Backward()
Forward()
counter += 1
if counter > iterations:
break
PoleConstraint()
for i in range(jointPositions.size()-1):
jointTransforms[i].origin = jointPositions[i]
var direction = jointPositions[i + 1] - jointPositions[i]
var targetRotation = Quaternion(jointStartDirection[i].normalized(), direction.normalized())
jointTransforms[i].basis = Basis(targetRotation * startRotation[i])
skeleton.set_bone_global_pose_override(bone_ids[i], jointTransforms[i], 1, true)
func Backward():
# Iterate through every position in the list until we reach the start of the chain
for i in range(jointPositions.size()-1, -1, -1):
# The last bone position should have the same position as the ikTarget
if i == jointPositions.size() - 1:
jointPositions[i] = ikTarget.origin
else:
jointPositions[i] = jointPositions[i + 1] + (jointPositions[i] - jointPositions[i + 1]).normalized() * boneLength[i]
func Forward():
# Iterate through every position in the list until we reach the end of the chain
for i in range(jointPositions.size()):
# The first bone position should have the same position as the startPosition
if i == 0:
jointPositions[i] = startPosition
else:
jointPositions[i] = jointPositions[i - 1] + (jointPositions[i] - jointPositions[i - 1]).normalized() * boneLength[i - 1]
func PoleConstraint():
if poleTarget != null and numberOfJoints < 4:
# Get the limb axis direction
var limbAxis = (jointPositions[2] - jointPositions[0]).normalized()
# Get the direction from the root joint to the pole target and mid joint
var poleDirection:Vector3 = Vector3(poleTarget.origin - jointPositions[0]).normalized()
var boneDirection:Vector3 = Vector3(jointPositions[1] - jointPositions[0]).normalized()
# Ortho-normalize the vectors
# Vector3.OrthoNormalize(ref limbAxis, ref poleDirection)
# Vector3.OrthoNormalize(ref limbAxis, ref boneDirection)
var on_pole = OrthoNormalize(limbAxis, poleDirection)
limbAxis = on_pole[0]
poleDirection = on_pole[1]
var on_bone = OrthoNormalize(limbAxis, boneDirection)
limbAxis = on_bone[0]
boneDirection = on_bone[1]
# Calculate the angle between the boneDirection vector and poleDirection vector
# Quaternion angle = Quaternion.FromToRotation(boneDirection, poleDirection)
var angle = Quaternion(boneDirection, poleDirection).normalized()
# Rotate the middle bone using the angle
jointPositions[1] = angle * (jointPositions[1] - jointPositions[0]) + jointPositions[0]
func bone_set_position(bone_index:int, bone_global_position:Vector3, lerp_amount:float = 1.0):
var bone_transform = skeleton.get_bone_global_pose_no_override(bone_index)
bone_transform.origin = bone_global_position
skeleton.set_bone_global_pose_override(bone_index, bone_transform, lerp_amount, true)
func bone_look_at(bone_index:int, bone_global_position:Vector3, target_global_position:Vector3, lerp_amount:float = 1.0):
var bone_transform = skeleton.get_bone_global_pose_no_override(bone_index)
#var target_pos = to_local(target_global_position)
#var target_pos = target_global_position
#var bone_origin = bone_transform.origin
var bone_origin = bone_global_position
#bone_transform.basis = bone_transform.basis.looking_at( -(target_global_position - bone_transform.origin).normalized())
bone_transform.basis = bone_transform.basis.looking_at( -(target_global_position - bone_global_position).normalized())
# bone_transform.origin = bone_origin
bone_transform.origin = bone_global_position
skeleton.set_bone_global_pose_override(bone_index, bone_transform, lerp_amount, true)
# Generated with ChatGPT
func OrthoNormalize(normal:Vector3, tangent:Vector3):
normal = normal.normalized()
tangent = tangent - normal * tangent.dot(normal)
tangent = tangent.normalized()
return [normal, tangent]