mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-01-05 22:09:56 +03:00
Revamping of the docs organisation for a more coherent TOC
Only the pages were moved so far and some empty ones created,
the up-to-date toctrees come in the next commit.
(cherry picked from commit b408bdb918)
This commit is contained in:
committed by
Rémi Verschelde
parent
6a730cb057
commit
5e05011eae
165
community/tutorials/3d/inverse_kinematics.rst
Normal file
165
community/tutorials/3d/inverse_kinematics.rst
Normal file
@@ -0,0 +1,165 @@
|
||||
.. _doc_inverse_kinematics:
|
||||
|
||||
Inverse kinematics
|
||||
==================
|
||||
|
||||
This tutorial is a follow-up of :ref:`doc_working_with_3d_skeletons`.
|
||||
|
||||
Before continuing on, I'd recommend reading some theory, the simplest
|
||||
article I find is this:
|
||||
|
||||
http://freespace.virgin.net/hugo.elias/models/m_ik2.htm
|
||||
|
||||
Initial problem
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Talking in Godot terminology, the task we want to solve here is position
|
||||
our 2 angles we talked about above so, that the tip of lowerarm bone is
|
||||
as close to target point, which is set by target Vector3() as possible
|
||||
using only rotations. This task is very calculation-intensive and never
|
||||
resolved by analytical equation solve. So, it is an underconstrained
|
||||
problem, which means there is unlimited number of solutions to the
|
||||
equation.
|
||||
|
||||
.. image:: /img/inverse_kinematics.png
|
||||
|
||||
For easy calculation, for this chapter we consider target is also
|
||||
child of Skeleton. If it is not the case for your setup you can always
|
||||
reparent it in your script, as you will save on calculations if you
|
||||
do.
|
||||
|
||||
In the picture you see angles alpha and beta. In this case we don't
|
||||
use poles and constraints, so we need to add our own. On the picture
|
||||
the angles are 2D angles living in plane which is defined by bone
|
||||
base, bone tip and target.
|
||||
|
||||
The rotation axis is easily calculated using cross-product of bone
|
||||
vector and target vector. The rotation in this case will be always in
|
||||
positive direction. If t is the Transform which we get from
|
||||
get_bone_global_pose() function, the bone vector is
|
||||
|
||||
::
|
||||
|
||||
t.basis[2]
|
||||
|
||||
So we have all information here to execute our algorithm.
|
||||
|
||||
In game dev it is common to resolve this problem by iteratively closing
|
||||
to the desired location, adding/subtracting small numbers to the angles
|
||||
until the distance change achieved is less than some small error value.
|
||||
Sounds easy enough, but there are Godot problems we need to resolve
|
||||
there to achieve our goal.
|
||||
|
||||
- **How to find coordinates of the tip of the bone?**
|
||||
- **How to find vector from bone base to target?**
|
||||
|
||||
For our goal (tip of the bone moved within area of target), we need to know
|
||||
where the tip of our IK bone is. As we don't use a leaf bone as IK bone, we
|
||||
know the coordinate of the bone base is the tip of parent bone. All these
|
||||
calculations are quite dependant on the skeleton's structure. You can use
|
||||
pre-calculated constants as well. You can add an extra bone for the tip of
|
||||
IK and calculate using that.
|
||||
|
||||
Implementation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We will just use exported variable for bone length to be easy.
|
||||
|
||||
::
|
||||
|
||||
export var IK_bone="lowerarm"
|
||||
export var IK_bone_length=1.0
|
||||
export var IK_error = 0.1
|
||||
|
||||
Now, we need to apply our transformations from IK bone to the base of
|
||||
chain. So we apply rotation to IK bone then move from our IK bone up to
|
||||
its parent, then apply rotation again, then move to the parent of
|
||||
current bone again, etc. So we need to limit our chain somewhat.
|
||||
|
||||
::
|
||||
|
||||
export var IK_limit = 2
|
||||
|
||||
For ``_ready()`` function:
|
||||
|
||||
::
|
||||
|
||||
var skel
|
||||
func _ready():
|
||||
skel = get_node("arm/Armature/Skeleton")
|
||||
set_process(true)
|
||||
|
||||
Now we can write our chain-passing function:
|
||||
|
||||
::
|
||||
|
||||
func pass_chain():
|
||||
var b = skel.find_bone(IK_bone)
|
||||
var l = IK_limit
|
||||
while b >= 0 and l > 0:
|
||||
print( "name":", skel.get_bone_name(b))
|
||||
print( "local transform":", skel.get_bone_pose(b))
|
||||
print( "global transform":", skel.get_bone_global_pose(b))
|
||||
b = skel.get_bone_parent(b)
|
||||
l = l - 1
|
||||
|
||||
And for the ``_process()`` function:
|
||||
|
||||
::
|
||||
|
||||
func _process(dt):
|
||||
pass_chain(dt)
|
||||
|
||||
Executing this script will just pass through bone chain printing bone
|
||||
transforms.
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
|
||||
export var IK_bone="lowerarm"
|
||||
export var IK_bone_length=1.0
|
||||
export var IK_error = 0.1
|
||||
export var IK_limit = 2
|
||||
var skel
|
||||
func _ready():
|
||||
skel = get_node("arm/Armature/Skeleton")
|
||||
set_process(true)
|
||||
func pass_chain(dt):
|
||||
var b = skel.find_bone(IK_bone)
|
||||
var l = IK_limit
|
||||
while b >= 0 and l > 0:
|
||||
print("name: ", skel.get_bone_name(b))
|
||||
print("local transform: ", skel.get_bone_pose(b))
|
||||
print( "global transform:", skel.get_bone_global_pose(b))
|
||||
b = skel.get_bone_parent(b)
|
||||
l = l - 1
|
||||
func _process(dt):
|
||||
pass_chain(dt)
|
||||
|
||||
Now we need to actually work with target. The target should be placed
|
||||
somewhere accessible. Since "arm" is imported scene, we better place
|
||||
target node within our top level scene. But for us to work with target
|
||||
easily its Transform should be on the same level as Skeleton.
|
||||
|
||||
To cope with this problem we create "target" node under our scene root
|
||||
node and at script run we will reparent it copying global transform,
|
||||
which will achieve wanted effect.
|
||||
|
||||
Create new Spatial node under root node and rename it to "target".
|
||||
Then modify ``_ready()`` function to look like this:
|
||||
|
||||
::
|
||||
|
||||
var skel
|
||||
var target
|
||||
func _ready():
|
||||
skel = get_node("arm/Armature/Skeleton")
|
||||
target = get_node("target")
|
||||
var ttrans = target.get_global_transform()
|
||||
remove_child(target)
|
||||
skel.add_child(target)
|
||||
target.set_global_transform(ttrans)
|
||||
set_process(true)
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
.. _doc_mesh_generation_with_heightmap_and_shaders:
|
||||
|
||||
Mesh generation with heightmap and shaders
|
||||
==========================================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This tutorial will help you to use Godot shaders to deform a plane
|
||||
mesh so it appears like a basic terrain. Remember that this solution
|
||||
has pros and cons.
|
||||
|
||||
Pros:
|
||||
|
||||
- Pretty easy to do.
|
||||
- This approach allows computation of LOD terrains.
|
||||
- The heightmap can be used in Godot to create a normal map.
|
||||
|
||||
Cons:
|
||||
|
||||
- The Vertex Shader can't re-compute normals of the faces. Thus, if
|
||||
your mesh is not static, this method will **not** work with shaded
|
||||
materials.
|
||||
- This tutorial uses a plane mesh imported from Blender to Godot
|
||||
Engine. Godot is able to create meshes as well.
|
||||
|
||||
See this tutorial as an introduction, not a method that you should
|
||||
employ in your games, except if you intend to do LOD. Otherwise, this is
|
||||
probably not the best way.
|
||||
|
||||
However, let's first create a heightmap,or a 2D representation of the terrain.
|
||||
To do this, I'll use GIMP, but you can use any image editor you like.
|
||||
|
||||
The heightmap
|
||||
-------------
|
||||
|
||||
We will use a few functions of GIMP image editor to produce a simple
|
||||
heightmap. Start GIMP and create a square image of 512x512 pixels.
|
||||
|
||||
.. image:: /img/1_GIMP_createImage512.png
|
||||
|
||||
You are now in front of a new, blank, square image.
|
||||
|
||||
.. image:: /img/2_GIMP.png
|
||||
|
||||
Then, use a filter to render some clouds on this new image.
|
||||
|
||||
.. image:: /img/3_GIMP_FilterRenderClouds.png
|
||||
|
||||
Parameter this filter to whatever you want. A white pixel corresponds
|
||||
to the highest point of the heightmap, a black pixel corresponds to
|
||||
the lowest one. So, darker regions are valleys and brighter are
|
||||
mountains. If you want, you can check "tileable" to render a heightmap
|
||||
that can be cloned and tiled close together with another one. X and Y
|
||||
size don't matter a lot as long as they are big enough to provide a
|
||||
decent ground. A value of 4.0 or 5.0 for both is nice. Click on the
|
||||
"New Seed" button to roll a dice and GIMP will create a new random
|
||||
heightmap. Once you are happy with the result, click "OK".
|
||||
|
||||
.. image:: /img/4_GIMP_Clouds.png
|
||||
|
||||
You can continue to edit your image if you wish. For our example,
|
||||
let's keep the heightmap as is, and let's export it to a PNG file, say
|
||||
"heightmap.png". Save it in your Godot project folder.
|
||||
|
||||
The plane mesh
|
||||
--------------
|
||||
|
||||
Now, we will need a plane mesh to import in Godot. Let's run Blender.
|
||||
|
||||
.. image:: /img/5_Blender.png
|
||||
|
||||
Remove the start cube mesh, then add a new plane to the scene.
|
||||
|
||||
.. image:: /img/6_Blender_CreatePlane.png
|
||||
|
||||
Zoom a bit, then switch to Edit mode (Tab key) and in the Tools
|
||||
buttongroup at the left, hit "Subdivide" 5 or 6 times.
|
||||
|
||||
.. image:: /img/7_Blender_subdivided.png
|
||||
|
||||
Your mesh is now subdivided, which means we added vertices to the
|
||||
plane mesh that we will later be able to move. Job's not finished yet:
|
||||
in order to texture this mesh a proper UV map is necessary. Currently,
|
||||
the default UV map contains only the 4 corner vertices we had at the
|
||||
beginning. However, we now have more, and we want to be able to
|
||||
texture over the whole mesh correctly.
|
||||
|
||||
If all the vertices of your mesh are not selected, select them all
|
||||
(hit "A"). They must appear orange, not black. Then, in the
|
||||
Shading/UVs button group to the left, click the "Unwrap" button (or
|
||||
simply hit "U") and select "Smart UV Project". Keep the default
|
||||
options and hit "Ok".
|
||||
|
||||
.. image:: /img/8_Blender_UVSmart.png
|
||||
|
||||
Now, we need to switch our view to "UV/Image editor".
|
||||
|
||||
.. image:: /img/9_Blender_UV_editor.png
|
||||
|
||||
Select all the vertices again ("A") then in the UV menu, select
|
||||
"Export UV Layout".
|
||||
|
||||
.. image:: /img/10_Blender_exportUV.png
|
||||
|
||||
Export the layout as a PNG file. Name it "plane.png" and save it in
|
||||
your Godot project folder. Now, let's export our mesh as an OBJ file.
|
||||
Top of the screen, click "File/Export/Wavefront (obj)". Save your
|
||||
object as "plane.obj" in your Godot project folder.
|
||||
|
||||
Shader magic
|
||||
------------
|
||||
|
||||
Let's now open Godot Editor.
|
||||
|
||||
Create a new project in the folder you previously created and name it
|
||||
what you want.
|
||||
|
||||
.. image:: /img/11_Godot.png
|
||||
|
||||
In our default scene (3D), create a root node "Spatial". Next, import
|
||||
the mesh OBJ file. Click "Import", choose "3D Mesh" and select your
|
||||
plane.obj file, set the target path as "/" (or wherever you want in
|
||||
your project folder).
|
||||
|
||||
.. image:: /img/12_Godot_ImportMesh.png
|
||||
|
||||
I like to check "Normals" in the import pop-up so the import will also
|
||||
consider faces normals, which can be useful (even if we don't use them
|
||||
in this tutorial). Your mesh is now displayed in the FileSystem in
|
||||
"res://".
|
||||
|
||||
.. image:: /img/13_Godot_ImportPopup.png
|
||||
|
||||
Create a MeshInstance node. In the Inspector, load the mesh we just
|
||||
imported. Select "plane.msh" and hit ok.
|
||||
|
||||
.. image:: /img/14_Godot_LoadMesh.png
|
||||
|
||||
Great! Our plane is now rendered in the 3D view.
|
||||
|
||||
.. image:: /img/15_Godot_MeshPlaneRendered.png
|
||||
|
||||
It is time to add some shader stuff. In the Inspector, in the
|
||||
"Material Override" line, add a "New ShaderMaterial". Edit it by
|
||||
clicking the ">" button just right to it.
|
||||
|
||||
.. image:: /img/16_Godot_ShaderMaterial.png
|
||||
|
||||
You have two ways to create a shader: by code (MaterialShader), or
|
||||
using a shader graph (MaterialShaderGraph). The second one is a bit
|
||||
more visual, but we will not cover it for now. Create a "New
|
||||
MaterialShader".
|
||||
|
||||
.. image:: /img/17_Godot_newMaterialShader.png
|
||||
|
||||
Edit it by clicking the ">" button just right to it. The Shaders
|
||||
editor opens.
|
||||
|
||||
.. image:: /img/18_Godot_ShaderEditorOpened.png
|
||||
|
||||
The Vertex tab is for the Vertex shader, and the Fragment tab is for
|
||||
the Fragment shader. No need to explain what both of them do, right?
|
||||
If so, head to the :ref:`doc_shading_language` page. Else, let's start with the
|
||||
Fragment shader. This one is used to texture the plane using an image.
|
||||
For this example, we will texture it with the heightmap image itself,
|
||||
so we'll actually see mountains as brighter regions and canyons as
|
||||
darker regions. Use this code:
|
||||
|
||||
::
|
||||
|
||||
uniform texture source;
|
||||
uniform color col;
|
||||
DIFFUSE = col.rgb * tex(source,UV).rgb;
|
||||
|
||||
This shader is very simple (it actually comes from the :ref:`doc_shading_language` page).
|
||||
What it basically does is take 2 parameters that we have to provide from
|
||||
outside the shader ("uniform"):
|
||||
|
||||
- the texture file
|
||||
- a color
|
||||
Then, we multiply every pixel of the image given by
|
||||
``tex(source, UV).rgb`` by the color defined ``col`` and we set it to
|
||||
DIFFUSE variable, which is the rendered color. Remember that the
|
||||
``UV`` variable is a shader variable that returns the 2D position of
|
||||
the pixel in the texture image, according to the vertex we are
|
||||
currently dealing with. That is the use of the UV Layout we made
|
||||
before. The color ``col`` is actually not necessary to display the
|
||||
texture, but it is interesting to play and see how it does, right?
|
||||
|
||||
However, the plane is displayed black! This is because we didn't set
|
||||
the texture file and the color to use.
|
||||
|
||||
.. image:: /img/19_Godot_BlackPlane.png
|
||||
|
||||
In the Inspector, click the "Previous" button to get back to the
|
||||
ShaderMaterial. This is where you want to set the texture and the
|
||||
color. In "Source", click "Load" and select the texture file
|
||||
"heightmap.png". But the mesh is still black! This is because our
|
||||
Fragment shader multiplies each pixel value of the texture by the
|
||||
``col`` parameter. However, this color is currently set to black
|
||||
(0,0,0), and as you know, 0\*x = 0 ;) . Just change the ``col``
|
||||
parameter to another color to see your texture appear:
|
||||
|
||||
.. image:: /img/20_Godot_TexturedPlane.png
|
||||
|
||||
Good. Now, the Vertex Shader.
|
||||
|
||||
The Vertex Shader is the first shader to be executed by the pipeline. It
|
||||
deals with vertices.
|
||||
|
||||
Click the "Vertex" tab to switch, and paste this code:
|
||||
|
||||
::
|
||||
|
||||
uniform texture source;
|
||||
uniform float height_range;
|
||||
vec2 xz = SRC_VERTEX.xz;
|
||||
float h = tex(source, UV).g * height_range;
|
||||
VERTEX = vec3(xz.x, h, xz.y);
|
||||
VERTEX = MODELVIEW_MATRIX * VERTEX;
|
||||
|
||||
This shader uses two "uniform" parameters. The ``source`` parameter is
|
||||
already set for the fragment shader. Thus, the same image will be used
|
||||
in this shader as the heightmap. The ``height_range`` parameter is a
|
||||
parameter that we will use to increase the height effect.
|
||||
|
||||
At line 3, we save the x and z position of the SRC_VERTEX, because we
|
||||
do not want them to change : the plane must remain square. Remember
|
||||
that Y axis corresponds to the "altitude", which is the only one we
|
||||
want to change with the heightmap.
|
||||
|
||||
At line 4, we compute an ``h`` variable by multiplying the pixel value
|
||||
at the UV position and the ``height_range``. As the heightmap is a
|
||||
greyscale image, all r, g and b channels contain the same value. I
|
||||
used ``g``, but any of r, g and b have the same effect.
|
||||
|
||||
At line 5, we set the current vertex' position at (xz.x, h, xz.y)
|
||||
position. Concerning xz.y remember that its type is "vec2". Thus, its
|
||||
components are x and y. The y component simply contains the z position
|
||||
we set at line 3.
|
||||
|
||||
Finally, at line 6, we multiply the vertex by the model/view matrix in
|
||||
order to set its position according to camera position. If you try to
|
||||
comment this line, you'll see that the mesh behaves weird as you move
|
||||
and rotate the camera.
|
||||
|
||||
That's all good, but our plane remains flat. This is because the
|
||||
``height_range`` value is 0. Increase this value to observe the mesh
|
||||
distort and take to form of the terrain we set before:
|
||||
|
||||
.. image:: /img/21_Godot_Fini.png
|
||||
350
community/tutorials/3d/working_with_3d_skeletons.rst
Normal file
350
community/tutorials/3d/working_with_3d_skeletons.rst
Normal file
@@ -0,0 +1,350 @@
|
||||
.. _doc_working_with_3d_skeletons:
|
||||
|
||||
Working with 3D skeletons
|
||||
=========================
|
||||
|
||||
Godot 3D skeleton support is currently quite rudimentary. The
|
||||
:ref:`class_Skeleton` node and class were designed mainly to support importing
|
||||
skeletal animations as a set of transformation matrices.
|
||||
|
||||
Skeleton node
|
||||
-------------
|
||||
|
||||
Skeleton node can be directly added anywhere you want on scene. Usually
|
||||
mesh is a child of Skeleton, as it easier to manipulate this way, as
|
||||
Transforms within skeleton are relative to where Skeleton is. But you
|
||||
can specify Skeleton node in every MeshInstance.
|
||||
|
||||
Being obvious, Skeleton is intended to deform meshes, and consists of
|
||||
structures called "bones". Each "bone" is represented as Transform, which is
|
||||
applied to a group of vertices within a mesh. You can directly control a group
|
||||
of vertices from Godot. For that please reference :ref:`class_MeshDataTool`
|
||||
class, method :ref:`set_vertex_bones <class_MeshDataTool_set_vertex_bones>`.
|
||||
This class is very powerful.
|
||||
|
||||
The "bones" are organized in hierarchy, every bone, except for root
|
||||
bone(s) have parent. Every bone have associated name you can use to
|
||||
refer to it (e.g. "root" or "hand.L", etc.). Also bones are all numbered,
|
||||
these numbers are bone IDs. Bone parents are referred by their numbered
|
||||
IDs.
|
||||
|
||||
For the rest of the article we consider the following scene:
|
||||
|
||||
::
|
||||
|
||||
main (Spatial) - script is always here
|
||||
== skel (Skeleton)
|
||||
==== mesh (MeshInstance)
|
||||
|
||||
This scene is imported from Blender. It contains arm mesh with 2 bones -
|
||||
upperarm and lowerarm, with lowerarm parented to upperarm.
|
||||
|
||||
Skeleton class
|
||||
--------------
|
||||
|
||||
You can view Godot internal help for descriptions of every function.
|
||||
Basically all operations on bones are done using their numeric ID. You
|
||||
can convert from name to numeric ID and vise versa.
|
||||
|
||||
**To find number of bones in skeleton we use get_bone_count()
|
||||
function**
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
var skel
|
||||
|
||||
func _ready():
|
||||
skel = get_node("skel")
|
||||
var id = skel.find_bone("upperarm")
|
||||
print("bone id:", id)
|
||||
var parent = skel.get_bone_parent(id)
|
||||
print("bone parent id:", id)
|
||||
|
||||
**to find ID for the bone, use find_bone() function**
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
var skel
|
||||
|
||||
func _ready():
|
||||
skel = get_node("skel")
|
||||
var id = skel.find_bone("upperarm")
|
||||
print("bone id:", id)
|
||||
|
||||
Now, we want to do something interesting with ID except for printing it.
|
||||
Also, we might need additional information - to find bone parents to
|
||||
complete chain, etc. This all is done with get/set_bone\_\* functions.
|
||||
|
||||
**To find bone parent we use get_bone_parent(id) function**
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
var skel
|
||||
|
||||
func _ready():
|
||||
skel = get_node("skel")
|
||||
var id = skel.find_bone("upperarm")
|
||||
print("bone id:", id)
|
||||
var parent = skel.get_bone_parent(id)
|
||||
print("bone parent id:", id)
|
||||
|
||||
Bone transforms is the thing why we're here at all. There are 3 kind of
|
||||
transforms - local, global, custom.
|
||||
|
||||
**To find bone local Transform we use get_bone_pose(id) function**
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
var skel
|
||||
|
||||
func _ready():
|
||||
skel = get_node("skel")
|
||||
var id = skel.find_bone("upperarm")
|
||||
print("bone id:", id)
|
||||
var parent = skel.get_bone_parent(id)
|
||||
print("bone parent id:", id)
|
||||
var t = skel.get_bone_pose(id)
|
||||
print("bone transform: ", t)
|
||||
|
||||
So we see 3x4 matrix there, with first column of 1s. What can we do
|
||||
about that? It is a Transform, so we can do everything we can do with
|
||||
Transform, basically translate, rotate and scale. Also we can multiply
|
||||
transforms to have complex transforms. Remember, "bones" in Godot are
|
||||
just Transforms over a group of vertices. Also we can copy Transforms of
|
||||
other objects there. So lets rotate our "upperarm" bone:
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
var skel
|
||||
var id
|
||||
|
||||
func _ready():
|
||||
skel = get_node("skel")
|
||||
id = skel.find_bone("upperarm")
|
||||
print("bone id:", id)
|
||||
var parent = skel.get_bone_parent(id)
|
||||
print("bone parent id:", id)
|
||||
var t = skel.get_bone_pose(id)
|
||||
print("bone transform: ", t)
|
||||
set_process(true)
|
||||
|
||||
func _process(dt):
|
||||
var t = skel.get_bone_pose(id)
|
||||
t = t.rotated(Vector3(0.0, 1.0, 0.0), 0.1 * dt)
|
||||
skel.set_bone_pose(id, t)
|
||||
|
||||
Now we can rotate individual bones. The same happens for scale and
|
||||
translate - try these on your own and see results.
|
||||
|
||||
What we used now was local pose. By default all bones are not modified.
|
||||
But this Transform tells us nothing about relationship between bones.
|
||||
This information is needed for quite a number of tasks. How can we get
|
||||
it? Here comes global transform:
|
||||
|
||||
**To find bone global Transform we use get_bone_global_pose(id)
|
||||
function**
|
||||
|
||||
We will find global Transform for lowerarm bone:
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
var skel
|
||||
|
||||
func _ready():
|
||||
skel = get_node("skel")
|
||||
var id = skel.find_bone("lowerarm")
|
||||
print("bone id:", id)
|
||||
var parent = skel.get_bone_parent(id)
|
||||
print("bone parent id:", id)
|
||||
var t = skel.get_bone_global_pose(id)
|
||||
print("bone transform: ", t)
|
||||
|
||||
As you see, this transform is not zeroed. While being called global, it
|
||||
is actually relative to Skeleton origin. For root bone, origin is always
|
||||
at 0 if not modified. Lets print origin for our lowerarm bone:
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
var skel
|
||||
|
||||
func _ready():
|
||||
skel = get_node("skel")
|
||||
var id = skel.find_bone("lowerarm")
|
||||
print("bone id:", id)
|
||||
var parent = skel.get_bone_parent(id)
|
||||
print("bone parent id:", id)
|
||||
var t = skel.get_bone_global_pose(id)
|
||||
print("bone origin: ", t.origin)
|
||||
|
||||
You will see a number. What does this number mean? It is a rotation
|
||||
point of Transform. So it is base part of the bone. In Blender you can
|
||||
go to Pose mode and try there to rotate bones - they will rotate around
|
||||
their origin. But what about tip? We can't know things like bone length,
|
||||
which we need for many things, without knowing tip location. For all
|
||||
bones in chain except for last one we can calculate tip location - it is
|
||||
simply a child bone origin. Yes, there are situations when this is not
|
||||
true, for non-connected bones. But that is OK for us for now, as it is
|
||||
not important regarding Transforms. But the leaf bone tip is nowhere to
|
||||
be found. Leaf bone is a bone without children. So you don't have any
|
||||
information about its tip. But this is not a showstopper. You can
|
||||
overcome this by either adding extra bone to the chain or just
|
||||
calculating leaf bone length in Blender and store the value in your
|
||||
script.
|
||||
|
||||
Using 3D "bones" for mesh control
|
||||
---------------------------------
|
||||
|
||||
Now as you know basics we can apply these to make full FK-control of our
|
||||
arm (FK is forward-kinematics)
|
||||
|
||||
To fully control our arm we need the following parameters:
|
||||
|
||||
- Upperarm angle x, y, z
|
||||
- Lowerarm angle x, y, z
|
||||
|
||||
All of these parameters can be set, incremented and decremented.
|
||||
|
||||
Create the following node tree:
|
||||
|
||||
::
|
||||
|
||||
main (Spatial) <- script is here
|
||||
+-arm (arm scene)
|
||||
+ DirectionLight (DirectionLight)
|
||||
+ Camera
|
||||
|
||||
Set up Camera so that arm is properly visible. Rotate DirectionLight
|
||||
so that arm is properly lit while in scene play mode.
|
||||
|
||||
Now we need to create new script under main:
|
||||
|
||||
First we setup parameters:
|
||||
|
||||
::
|
||||
|
||||
var lowerarm_angle = Vector3()
|
||||
var upperarm_angle = Vector3()
|
||||
|
||||
Now we need to setup a way to change them. Let us use keys for that.
|
||||
|
||||
Please create 7 actions under project settings:
|
||||
|
||||
- **selext_x** - bind to X key
|
||||
- **selext_y** - bind to Y key
|
||||
- **selext_z** - bind to Z key
|
||||
- **select_upperarm** - bind to key 1
|
||||
- **select_lowerarm** - bind to key 2
|
||||
- **increment** - bind to key numpad +
|
||||
- **decrement** - bind to key numpad -
|
||||
|
||||
So now we want to adjust the above parameters. Therefore we create code
|
||||
which does that:
|
||||
|
||||
::
|
||||
|
||||
func _ready():
|
||||
set_process(true)
|
||||
var bone = "upperarm"
|
||||
var coordinate = 0
|
||||
func _process(dt):
|
||||
if Input.is_action_pressed("select_x"):
|
||||
coordinate = 0
|
||||
elif Input.is_action_pressed("select_y"):
|
||||
coordinate = 1
|
||||
elif Input.is_action_pressed("select_z"):
|
||||
coordinate = 2
|
||||
elif Input.is_action_pressed("select_upperarm"):
|
||||
bone = "upperarm"
|
||||
elif Input.is_action_pressed("select_lowerarm"):
|
||||
bone = "lowerarm"
|
||||
elif Input.is_action_pressed("increment"):
|
||||
if bone == "lowerarm":
|
||||
lowerarm_angle[coordinate] += 1
|
||||
elif bone == "upperarm":
|
||||
upperarm_angle[coordinate] += 1
|
||||
|
||||
The full code for arm control is this:
|
||||
|
||||
::
|
||||
|
||||
extends Spatial
|
||||
|
||||
# member variables here, example:
|
||||
# var a=2
|
||||
# var b="textvar"
|
||||
var upperarm_angle = Vector3()
|
||||
var lowerarm_angle = Vector3()
|
||||
var skel
|
||||
|
||||
func _ready():
|
||||
skel = get_node("arm/Armature/Skeleton")
|
||||
set_process(true)
|
||||
var bone = "upperarm"
|
||||
var coordinate = 0
|
||||
func set_bone_rot(bone, ang):
|
||||
var b = skel.find_bone(bone)
|
||||
var rest = skel.get_bone_rest(b)
|
||||
var newpose = rest.rotated(Vector3(1.0, 0.0, 0.0), ang.x)
|
||||
var newpose = newpose.rotated(Vector3(0.0, 1.0, 0.0), ang.y)
|
||||
var newpose = newpose.rotated(Vector3(0.0, 0.0, 1.0), ang.z)
|
||||
skel.set_bone_pose(b, newpose)
|
||||
|
||||
func _process(dt):
|
||||
if Input.is_action_pressed("select_x"):
|
||||
coordinate = 0
|
||||
elif Input.is_action_pressed("select_y"):
|
||||
coordinate = 1
|
||||
elif Input.is_action_pressed("select_z"):
|
||||
coordinate = 2
|
||||
elif Input.is_action_pressed("select_upperarm"):
|
||||
bone = "upperarm"
|
||||
elif Input.is_action_pressed("select_lowerarm"):
|
||||
bone = "lowerarm"
|
||||
elif Input.is_action_pressed("increment"):
|
||||
if bone == "lowerarm":
|
||||
lowerarm_angle[coordinate] += 1
|
||||
elif bone == "upperarm":
|
||||
upperarm_angle[coordinate] += 1
|
||||
elif Input.is_action_pressed("decrement"):
|
||||
if bone == "lowerarm":
|
||||
lowerarm_angle[coordinate] -= 1
|
||||
elif bone == "upperarm":
|
||||
upperarm_angle[coordinate] -= 1
|
||||
set_bone_rot("lowerarm", lowerarm_angle)
|
||||
set_bone_rot("upperarm", upperarm_angle)
|
||||
|
||||
Pressing keys 1/2 select upperarm/lowerarm, select axis by pressing x,
|
||||
y, z, rotate using numpad "+"/"-"
|
||||
|
||||
This way you fully control your arm in FK mode using 2 bones. You can
|
||||
add additional bones and/or improve "feel" of the interface by using
|
||||
coefficients for the change. I recommend you play with this example a
|
||||
lot before going to next part.
|
||||
|
||||
You can clone the demo code for this chapter using
|
||||
|
||||
::
|
||||
|
||||
git clone git@github.com:slapin/godot-skel3d.git
|
||||
cd demo1
|
||||
|
||||
Or you can browse it using web-interface:
|
||||
|
||||
https://github.com/slapin/godot-skel3d
|
||||
|
||||
Using 3D "bones" to implement Inverse Kinematics
|
||||
------------------------------------------------
|
||||
|
||||
See :ref:`doc_inverse_kinematics`.
|
||||
|
||||
Using 3D "bones" to implement ragdoll-like physics
|
||||
--------------------------------------------------
|
||||
|
||||
TODO.
|
||||
Reference in New Issue
Block a user