Browse Source

Procedural Geometry tutorial (#2375)

* intro for procedural geometry

* added using arraymesh tutorial

* added immediategeometry tutorial

* added final procedural geometry tutorial sections
Clay John 6 năm trước cách đây
mục cha
commit
23b5ff7989

+ 6 - 1
about/docs_changelog.rst

@@ -73,7 +73,12 @@ Multi-threading
 Creating content
 ^^^^^^^^^^^^^^^^
 
-- :ref:`doc_procedural_geometry`
+Procedural geometry series:
+  - :ref:`Procedural geometry <toc-procedural_geometry>`
+  - :ref:`doc_arraymesh`
+  - :ref:`doc_surfacetool`
+  - :ref:`doc_meshdatatool`
+  - :ref:`doc_immediategeometry`
 
 Optimization
 ^^^^^^^^^^^^

+ 1 - 1
tutorials/content/index.rst

@@ -5,6 +5,6 @@ Creating content
    :maxdepth: 1
    :name: toc-tutorials-content
 
-   procedural_geometry
+   procedural_geometry/index
    making_trees
 

+ 0 - 144
tutorials/content/procedural_geometry.rst

@@ -1,144 +0,0 @@
-.. _doc_procedural_geometry:
-
-Procedural geometry generation
-==============================
-
-Users often ask how to generate geometry from code. This is not very complicated, but it's not obvious.
-Godot provides a few classes entirely dedicated to make it this easy. Still, the best tool for the job depends
-entirely on the use case.
-
-SurfaceTool
------------
-
-This is the most common helper. :ref:`SurfaceTool<class_SurfaceTool>` is a class you can instantiate to generate :ref:`Meshes<class_Mesh>`, specifically *Mesh Surfaces*.
-
-It has a similar API to OpenGL 1.x, and it's meant for static content. This means, the mesh is generated once and then used.
-
-Here is a simple example of how to use it to add a single triangle.
-
-.. tabs::
- .. code-tab:: gdscript GDScript
-
-    var st = SurfaceTool.new()
-
-    st.begin(Mesh.PRIMITIVE_TRIANGLES)
-
-    # Prepare attributes for add_vertex.
-    st.add_normal(Vector3(0, 0, 1))
-    st.add_uv(Vector2(0, 0))
-    # Call last for each vertex, adds the above attributes.
-    st.add_vertex(Vector3(-1, -1, 0))
-
-    st.add_normal(Vector3(0, 0, 1))
-    st.add_uv(Vector2(0, 1))
-    st.add_vertex(Vector3(-1, 1, 0))
-
-    st.add_normal(Vector3(0, 0, 1))
-    st.add_uv(Vector2(1, 1))
-    st.add_vertex(Vector3(1, 1, 0))
-
-    # Create indices, indices are optional.
-    st.index()
-
-    # Commit to a mesh.
-    var mesh = st.commit()
-
-Just explore the APIs and the possibilities.
-
-ImmediateGeometry
------------------
-
-Unlike *SurfaceTool*, :ref:`ImmediateGeometry<class_ImmediateGeometry>` is an actual node. It's similar in the "OpenGL 1.x" style API,
-but it's actually designed to create content on the fly and modify it every frame efficiently.
-
-Generating complex geometry (several thousand vertices) with this node is inefficient, even if it's done only once. Instead, *ImmediateGeometry* is designed to generate simple geometry that changes every frame.
-
-It's used similar to *SurfaceTool*.
-
-.. tabs::
- .. code-tab:: gdscript GDScript
-
-    extends ImmediateGeometry
-
-    void _process(delta):
-        # Clean up before drawing.
-        clear()
-
-        # Begin draw.
-        begin(Mesh.PRIMITIVE_TRIANGLES)
-
-        # Prepare attributes for add_vertex.
-        set_normal( Vector3(0, 0, 1))
-        set_uv(Vector2(0, 0))
-        # Call last for each vertex, adds the above attributes.
-        add_vertex(Vector3(-1, -1, 0))
-
-        set_normal(Vector3(0, 0, 1))
-        set_uv(Vector2(0, 1))
-        add_vertex(Vector3(-1, 1, 0))
-
-        set_normal(Vector3(0, 0, 1))
-        set_uv(Vector2(1, 1))
-        add_vertex(Vector3(1, 1, 0))
-
-        # End drawing.
-        end()
-
-Arrays
-------
-
-Lastly, the final way to do this is to create arrays themselves. This is the most efficient way to create static geometry, and is only
-recommended when SurfaceTool is not fast enough.
-
-Similar code as before, but draw a square using indices:
-
-
-.. tabs::
- .. code-tab:: gdscript GDScript
-
-    var arrays = []
-    arrays.resize(Mesh.ARRAY_MAX)
-
-    var normal_array = []
-    var uv_array = []
-    var vertex_array = []
-    var index_array = []
-
-    normal_array.resize(4)
-    uv_array.resize(4)
-    vertex_array.resize(4)
-    index_array.resize(6)
-
-    normal_array[0] = Vector3(0, 0, 1)
-    uv_array[0] = Vector2(0, 0)
-    vertex_array[0] = Vector3(-1, -1, 0)
-
-    normal_array[1] = Vector3(0, 0, 1)
-    uv_array[1] = Vector2(0,1)
-    vertex_array[1] = Vector3(-1, 1, 0)
-
-    normal_array[2] = Vector3(0, 0, 1)
-    uv_array[2] = Vector2(1, 1)
-    vertex_array[2] = Vector3(1, 1, 0)
-
-    normal_array[3] = Vector3(0, 0, 1)
-    uv_array[3] = Vector2(1, 0)
-    vertex_array[3] = Vector3(1, -1, 0)
-
-    # Indices are optional in Godot, but if they exist they are used.
-    index_array[0] = 0
-    index_array[1] = 1
-    index_array[2] = 2
-
-    index_array[3] = 2
-    index_array[4] = 3
-    index_array[5] = 0
-
-    arrays[Mesh.ARRAY_VERTEX] = vertex_array
-    arrays[Mesh.ARRAY_NORMAL] = normal_array
-    arrays[Mesh.ARRAY_TEX_UV] = uv_array
-    arrays[Mesh.ARRAY_INDEX] = index_array
-
-    var mesh = ArrayMesh.new()
-
-    mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,arrays)

+ 216 - 0
tutorials/content/procedural_geometry/arraymesh.rst

@@ -0,0 +1,216 @@
+.. _doc_arraymesh:
+
+Using the ArrayMesh
+===================
+
+This tutorial will present the basics of using an :ref:`ArrayMesh <class_arraymesh>`
+
+To do so, we will use the function :ref:`add_surface_from_arrays() <class_ArrayMesh_method_add_surface_from_arrays>`,
+which takes up to four parameters. The first two are required, while the second two are optional.
+
+The first is the ``PrimitiveType``, this is an OpenGL concept that instructs the GPU
+how to arrange the primitive based on the vertices given whether it is triangles,
+lines, points, etc. A complete list can be found under the :ref:`Mesh <class_mesh>` 
+class reference page.
+
+The second is the actual Array that stores the mesh information. The array is a normal Godot array that
+is constructed with empty brackets ``[]``. It stores a ``Pool**Array`` (e.g. PoolVector3Array,
+PoolIntArray, etc.) for each type of information.
+
+- ``ARRAY_VERTEX`` = 0 | PoolVector3Array or PoolVector2Array
+- ``ARRAY_NORMAL`` = 1 | PoolVector3Array
+- ``ARRAY_TANGENT`` = 2 | PoolRealArray of groups of 4 floats. first 3 floats determine the tangent, and
+  the last the binormal direction as -1 or 1.
+- ``ARRAY_COLOR`` = 3 | PoolColorArray
+- ``ARRAY_TEX_UV`` = 4 | PoolVector2Array or PoolVector3Array
+- ``ARRAY_TEX_UV2`` = 5 | PoolVector2Array or PoolVector3Array
+- ``ARRAY_BONES`` = 6 | PoolRealArray of groups of 4 floats or PoolIntArray of groups of 4 ints
+- ``ARRAY_WEIGHTS`` = 7 | PoolRealArray of groups of 4 floats
+- ``ARRAY_INDEX`` = 8 | PoolIntArray
+
+The Array of vertices is always required. All the others are optional and will only be used if included.
+
+Each array needs to have the same number of elements as the vertex array except for the index array.
+For arrays like tangents, an element is a group of 4 floats. So the array size will be four times
+the size of the vertex array size, but they will have the same number of elements
+
+The index array is unique.
+
+The third parameter is an array of blendshapes for the Mesh to use. While this tutorial does not cover
+using blendshapes, it is possible to specify them when creating a surface from arrays.
+
+The last parameter is the compress flags which specifies which arrays to store with half as many bits. The
+values can be found in the classref for VisualServer under ArrayFormat.//I think I can link to this
+
+For normal usage you will find it is best to leave the last two parameters empty.
+
+ArrayMesh
+---------
+
+Add an :ref:`ArrayMesh <class_arraymesh>` to a MeshInstance. Normally, adding an ArrayMesh in
+the editor is not useful, but in this case it allows as to access the ArrayMesh from code
+without creating one.
+
+Next, add a script to the MeshInstance.
+
+Under ``_ready()``, create a new Array.
+
+.. tabs::
+  .. code-tab:: gdscript GDScript
+
+    var arr = []
+
+This will be the array that we keep our surface information in, it will hold
+all the arrays of data that the surface needs. Godot will expect it to be of
+size ``Mesh.ARRAY_MAX``, so resize it accordingly.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    var arr = []
+    arr.resize(Mesh.ARRAY_MAX)
+
+Next create the arrays for each data type you will use.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    var verts = PoolVector3Array()
+    var uvs = PoolVector2Array()
+    var normals = PoolVector3Array()
+    var indices = PoolIntArray()
+
+Once you have filled your data arrays with your geometry you can create a mesh
+by adding each array to ``surface_array`` and then committing to the mesh.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+	    arr[Mesh.ARRAY_VERTEX] = verts
+	    arr[Mesh.ARRAY_TEX_UV] = uvs
+	    arr[Mesh.ARRAY_NORMAL] = normals
+	    arr[Mesh.ARRAY_INDEX] = indices
+
+	    mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr) # No blendshapes or compression used
+
+.. note:: In this example, we used ``Mesh.PRIMITIVE_TRIANGLES``, but you can use any primitive type
+          available from mesh.
+
+Put together the full code looks like:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    extends MeshInstance
+
+    func _ready():
+	    var arr = []
+	    arr.resize(Mesh.ARRAY_MAX)
+
+	    # PoolVectorXXArrays for mesh construction
+	    var verts = PoolVector3Array()
+	    var uvs = PoolVector2Array()
+	    var normals = PoolVector3Array()
+	    var indices = PoolIntArray()
+
+      #######################################
+      ## Insert code here to generate mesh ##
+      #######################################
+
+	    # Assign arrays to mesh array
+	    arr[Mesh.ARRAY_VERTEX] = verts
+	    arr[Mesh.ARRAY_TEX_UV] = uvs
+	    arr[Mesh.ARRAY_NORMAL] = normals
+	    arr[Mesh.ARRAY_INDEX] = indices
+
+	    # create mesh surface from mesh array
+	    mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arr) # No blendshapes or compression used
+
+
+The code that goes in the middle can be whatever you want. Below we will present some example code that
+could go in the middle.
+
+Generating Geometry
+-------------------
+
+Here is sample code for generating a sphere. Although the code is presented in
+GDScript, there is nothing Godot specific about the approach to generating it.
+This implementation has nothing in particular to do with ArrayMeshes and is just a
+generic approach to generating a sphere. If you are having trouble understanding it
+or want to learn more about procedural geometry in general, you can use any tutorial
+that you find online.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    extends MeshInstance
+
+    var rings = 50
+    var radial_segments = 50
+    var height = 1
+    var radius = 1
+
+    func _ready():
+
+      ##Set up the PoolVectorXXArrays
+
+	    # Vertex indices
+	    var thisrow = 0
+	    var prevrow = 0
+	    var point = 0
+
+	    # Loop over rings
+	    for i in range(rings+1):
+		    var v = float(i) / (rings)
+		    var w = sin(PI * v)
+		    var y = cos(PI * v)
+
+		    # Loop over segments in ring
+		    for j in range(radial_segments):
+			    var u = float(j) / (radial_segments)
+			    var x = sin(u * PI * 2.0)
+			    var z = cos(u * PI * 2.0)
+			    var vert = Vector3(x * radius * w, y, z * radius * w)
+			    verts.append(vert)
+			    normals.append(vert.normalized())
+			    uvs.append(Vector2(u, v))
+			    point += 1
+
+			    # Create triangles in ring using indices
+			    if (i>0 and j>0):
+				    indices.append(prevrow + j - 1)
+				    indices.append(prevrow + j)
+				    indices.append(thisrow + j - 1)
+
+				    indices.append(prevrow + j)
+				    indices.append(thisrow + j)
+				    indices.append(thisrow + j - 1)
+		    if (i>0):
+			    indices.append(prevrow + radial_segments - 1)
+			    indices.append(prevrow)
+			    indices.append(thisrow + radial_segments - 1)
+			
+			    indices.append(prevrow)
+			    indices.append(prevrow + radial_segments)
+			    indices.append(thisrow + radial_segments - 1)
+		    prevrow = thisrow
+		    thisrow = point
+
+      ##Commit to the ArrayMesh
+
+Combined with the code above, this code will generate a sphere.
+
+When it comes to generating geometry with the ArrayMesh you need to understand what goes 
+in each array and then you can follow tutorials for any language/engine and convert it into Godot.
+
+Saving
+------
+
+Finally, Godot provides a single method to save ArrayMeshes using the :ref:`ResourceSaver <class_resourcesaver>`
+class. This is useful when you want to generate a mesh and then use it later without having to re-generate.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    # Saves mesh to a .tres file with compression enabled
+    ResourceSaver.save("res://sphere.tres", mesh, 32)

+ 67 - 0
tutorials/content/procedural_geometry/immediategeometry.rst

@@ -0,0 +1,67 @@
+.. _doc_immediategeometry:
+
+Using ImmediateGeometry
+=======================
+
+Unlike the SurfaceTool or ArrayMesh, :ref:`ImmediateGeometry <class_ImmediateGeometry>` is an actual
+node. Being an node makes it quick to add to a scene and get visual output. It uses an "OpenGL 1.x" style
+API like the SurfaceTool, but it's actually designed to create content on the fly.
+
+Generating complex geometry (several thousand vertices) with this node is inefficient, even if it's
+done only once. Instead, it is designed to generate simple geometry that changes every frame.
+
+Before starting, you should clear the clear the geometry by calling ``clear()``. This ensures that
+you are not building upon the geometry from the previous frame. If you want to keep geometry between frames, do
+not call ``clear()``.
+
+To begin generating geometry you must call ``begin()``. ``begin()`` takes a ``PrimitiveType`` as an argument.
+``PrimitiveType`` is an OpenGL concept that instructs the GPU how to arrange the primitive based on the
+vertices given whether it is triangles, lines, points, etc. A complete list can be found under
+the :ref:`Mesh <class_mesh>` class reference page.
+
+Once you have called ``begin()`` you are ready to start adding vertices. You add vertices one at a time.
+First you add vertex specific attributes such as normals or UVs using ``set_****()`` (e.g. ``set_normal()``).
+Then you call ``add_vertex()`` to add a vertex with those attributes. For example:
+
+.. tabs::
+  .. code-tab:: gdscript GDScript
+
+    # Add a vertex with normal and uv
+    set_normal(Vector3(0, 1, 0))
+    set_uv(Vector2(1, 1))
+    add_vertex(Vector3(0, 0, 1))
+
+Only attributes added before the call to ``add_vertex()`` will be included in that vertex.
+
+Finally, once you have added all your vertices call ``end()`` to signal that you have finished generating the mesh.
+
+The example code below draws a single triangle.
+
+.. tabs::
+  .. code-tab:: gdscript GDScript
+
+    extends ImmediateGeometry
+
+    void _process(delta):
+        # Clean up before drawing.
+        clear()
+
+        # Begin draw.
+        begin(Mesh.PRIMITIVE_TRIANGLES)
+
+        # Prepare attributes for add_vertex.
+        set_normal( Vector3(0, 0, 1))
+        set_uv(Vector2(0, 0))
+        # Call last for each vertex, adds the above attributes.
+        add_vertex(Vector3(-1, -1, 0))
+
+        set_normal(Vector3(0, 0, 1))
+        set_uv(Vector2(0, 1))
+        add_vertex(Vector3(-1, 1, 0))
+
+        set_normal(Vector3(0, 0, 1))
+        set_uv(Vector2(1, 1))
+        add_vertex(Vector3(1, 1, 0))
+
+        # End drawing.
+        end()

+ 132 - 0
tutorials/content/procedural_geometry/index.rst

@@ -0,0 +1,132 @@
+Procedural Geometry
+===================
+
+There are many ways to procedurally generate geometry in Godot. In this tutorial series
+we will explore a few of them. Each technique has its own benefits and drawbacks, so 
+it is best to understand each one and how it can be useful in a given situation. 
+
+.. toctree::
+   :maxdepth: 1
+   :name: toc-procedural_geometry
+
+   arraymesh
+   meshdatatool
+   surfacetool
+   immediategeometry
+
+What is geometry?
+-----------------
+
+Geometry is a fancy way of saying shape. In computer graphics, geometry is typically represented
+by an array of positions called "vertices". In Godot, geometry is represented by Meshes.
+
+What is a Mesh?
+---------------
+
+Many things in Godot have mesh in their name: the :ref:`Mesh <class_Mesh>`, the :ref:`ArrayMesh <class_ArrayMesh>`, 
+the :ref:`MeshInstance <class_MeshInstance>`, the :ref:`MultiMesh <class_MultiMesh>`, and 
+the :ref:`MultiMeshInstance <class_MultiMeshInstance>`. While they are all related, they have slightly different uses. 
+
+Meshes and ArrayMeshes are resources that are drawn using a MeshInstance node. Resources like 
+Meshes and ArrayMeshes cannot be added to the scene directly. A MeshInstance represents one 
+instance of a mesh in your scene. You can reuse a single mesh in multiple MeshInstances 
+to draw it in different parts of your scene with different materials or transformations (scale, 
+rotation, position etc.). 
+
+If you are going to draw the same object many times, it can be helpful to use a MultiMesh with 
+a MultiMeshInstance. The MultiMeshInstance draws meshes thousands of times very 
+cheaply. It takes advantage of hardware instancing in order to do so. The drawback with 
+using a MultiMeshInstance is that you are limited to one material for all instances. It uses an
+instance array to store different colors and transformations for each instance, but all the
+instances use the same material.
+
+What a Mesh is
+--------------
+
+A Mesh is composed of one or more surfaces. A surface is an array composed of multiple sub-arrays
+containing vertices, normals, UVs, etc. Normally the process of constructing surfaces and meshes is
+hidden from the user in the :ref:`VisualServer <class_VisualServer>`, but with ArrayMeshes, the user can construct a Mesh
+manually by passing in an array containing the surface information.
+
+Surfaces
+^^^^^^^^
+
+Each surface has its own material. Alternatively, you can override the material for all surfaces
+in the Mesh when you use a MeshInstance using ``MeshInstance.override_material``.
+
+Surface array
+^^^^^^^^^^^^^
+
+The surface array is an array of length ``ArrayMesh.ARRAY_MAX``. Each position in the array is
+filled with a sub-array containing per-vertex information. For example, the array located at 
+``ArrayMesh.ARRAY_NORMAL`` is a :ref:`PoolVector3Array <class_PoolVector3Array>` of vertex normals. 
+
+The surface array can be indexed or non-indexed. Creating a non-indexed array is as easy as not assigning
+an array at the index ``ArrayMesh.ARRAY_INDEX``. A non-indexed array stores unique vertex information for
+every triangle, meaning that when two triangle share a vertex, the vertex is duplicated in the array. An 
+indexed surface array only stores vertex information for each unique vertex and then also stores an array 
+of indices which maps out how to construct the triangles from the vertex array. In general, using an indexed 
+array is faster, but it means you have to share vertex data between triangles, which is not always desired 
+(e.g. when you want per-face normals).
+
+Tools
+-----
+
+Godot provides different ways of accessing and working with geometry. More information on each will
+be provided in the following tutorials.
+
+ArrayMesh
+^^^^^^^^^
+
+The ArrayMesh resource extends Mesh to add a few different quality of life functions, and most 
+importantly, the ability to construct a Mesh surface through scripting.
+
+For more information about the ArrayMesh, please see the :ref:`ArrayMesh tutorial <doc_arraymesh>`.
+
+MeshDataTool
+^^^^^^^^^^^^
+
+The MeshDataTool is a resource that converts Mesh data into arrays of vertices, faces, and edges that can 
+be modified at runtime.
+
+For more information about the MeshDataTool, please see the :ref:`MeshDataTool tutorial <doc_meshdatatool>`.
+
+SurfaceTool
+^^^^^^^^^^^
+
+The SurfaceTool allows the creation of Meshes using an OpenGL 1.x immediate mode style interface. 
+
+For more information about the SurfaceTool, please see the :ref:`SurfaceTool tutorial <doc_surfacetool>`.
+
+ImmediateGeometry
+^^^^^^^^^^^^^^^^^
+
+ImmediateGeometry is a node that uses an immediate mode style interface (like SurfaceTool) to draw objects. The
+difference between ImmediateGeometry and the SurfaceTool is that ImmediateGeometry is a node itself that can be
+added to the scene tree and is drawn directly from the code. The SurfaceTool generates a Mesh that needs to be added 
+a MeshInstance to be seen. 
+
+ImmediateGeometry is useful for prototyping because of the straightforward API, but it is slow because the geometry
+is rebuilt every frame. It is most useful for quickly adding simple geometry to debug visually (e.g. by drawing lines to 
+visualize physics raycasts etc.).
+
+For more information about ImmediateGeometry, please see the :ref:`ImmediateGeometry tutorial <doc_immediategeometry>`.
+
+Which one should I use?
+-----------------------
+
+Which method you use depends on what you are trying to do and what kind of procedure you are comfortable with.
+
+Both SurfaceTool and ArrayMesh are excellent for generating static geometry (meshes) that don't change over time.
+
+Using an ArrayMesh is slightly faster than using a SurfaceTool, but the API is more a little more challenging.
+Additionally, SurfaceTool has a few quality of life methods such as ``generate_normals()`` and ``index()``.
+
+ImmediateGeometry regenerates the mesh every frame, so it is much slower than ArrayMesh or SurfaceTool. However, if you
+need the geometry to change every frame anyway it provides a much easier interface that may even be a little faster than generating
+an ArrayMesh every frame.
+
+The MeshDataTool is not fast, but it gives you access to all kinds of properties of the mesh that you don't get with the others 
+(edges, faces, etc.). It is incredibly useful when you need that sort of data to transform the mesh, but it is not a good idea
+to use if that information is not needed. The MeshDataTool is best used if you are going to be using an algorithm that requires
+access to the face or edge array.

+ 119 - 0
tutorials/content/procedural_geometry/meshdatatool.rst

@@ -0,0 +1,119 @@
+.. _doc_meshdatatool:
+
+Using the MeshDataTool
+======================
+
+The MeshDataTool is not used to generate geometry. But it is helpful for dynamically altering geometry, for example
+if you want to write a script to tessellate, simplify, or deform meshes. 
+
+The MeshDataTool is not as fast as altering arrays directly using ArrayMesh. However, it provides more information
+and tools to work with meshes than the ArrayMesh does. When the MeshDataTool 
+is used, it calculates mesh data that is not available in ArrayMeshes such as faces and edges, which are necessary
+for certain mesh algorithms. If you do not need this extra information then it may be better to use an ArrayMesh.
+
+.. note:: MeshDataTool can only be used on Meshes that use the PrimitiveType ``Mesh.PRIMITIVE_TRIANGLES``.
+
+As an example, let's walk through the process of deforming the mesh generated in the :ref:`ArrayMesh tutorial <doc_arraymesh>`.
+
+Assume the mesh is stored in an ArrayMesh named ``mesh``. We then initialize the MeshDataTool from
+``mesh`` by calling ``create_from_surface()``. If there is already data initialized in the MeshDataTool
+calling ``create_from_surface()`` will clear it for you. Alternatively, you can call ``clear()`` yourself
+before re-using the MeshDataTool
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+  var mdt = MeshDataTool.new()
+  mdt.create_from_surface(mesh)
+
+``create_from_surface()`` uses the vertex arrays from the ArrayMesh to calculate two additional arrays,
+one for edges and one for faces.
+
+An edge is a connection between any two vertices. Each edge in the edge array contains a reference to
+the two vertices it is composed of, and up to two faces that it is contained within.
+
+A face is a triangle made up of three vertices and three corresponding edges. Each face in the face array contains
+a reference to the three triangles and three edges it is composed of.
+
+The vertex array contains edges, faces, normals, color, tangent, uv, uv2, bones, and weight information connected
+with each vertex.
+
+To access information from these arrays you use a function of the form ``get_****()``:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+  mdt.get_vertex_count() # returns number of vertices in vertex array
+  mdt.get_vertex_faces(0) # returns array of faces that contain vertex[0]
+  mdt.get_face_normal(1) # calculates and returns face normal
+  mdt.get_edge_vertex(10, 1) # returns the second vertex comprsing edge at index 10
+
+What you choose to do with these functions is up to you. A common use case is to iterate over all vertices
+and transform them in some way:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+  for i in range(get_vertex_count):
+    var vert = mdt.get_vertex(i)
+    vert *= 2.0 # scales the vertex by doubling size
+    mdt.set_vertex(i, vert)
+
+Finally, ``commit_to_surface()`` adds a new surface to the ArrayMesh. So if you are dynamically
+updating an existing ArrayMesh, first delete the existing surface before adding a new one.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+  mesh.surface_remove(0) # delete the first surface of the mesh
+  mdt.commit_to_surface(mesh)
+
+Below is a complete example that creates a pulsing blob complete with new normals and vertex colors.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    extends MeshInstance
+
+    var sn = OpenSimplexNoise.new()
+    var mdt = MeshDataTool.new()
+
+    func _ready():
+	    sn.period = 0.7
+	
+	    mdt.create_from_surface(mesh, 0)
+	
+	    for i in range(mdt.get_vertex_count()):
+		    var vertex = mdt.get_vertex(i).normalized()
+		    # Push out vertex by noise
+		    vertex = vertex * (sn.get_noise_3dv(vertex)*0.5+0.75)
+		    mdt.set_vertex(i, vertex)
+
+    	# Calculate vertex normals, face-by-face
+	    for i in range(mdt.get_face_count()):
+		    # Get the index in the vertex array
+		    var a = mdt.get_face_vertex(i, 0)
+		    var b = mdt.get_face_vertex(i, 1)
+		    var c = mdt.get_face_vertex(i, 2)
+		    # Get vertex position using vertex index
+		    var ap = mdt.get_vertex(a)
+		    var bp = mdt.get_vertex(b)
+		    var cp = mdt.get_vertex(c)
+		    # Calculate face normal
+		    var n = (bp - cp).cross(ap - bp).normalized()
+		    # Add face normal to current vertex normal
+		    # this will not result in perfect normals, but it will be close
+		    mdt.set_vertex_normal(a, n + mdt.get_vertex_normal(a))
+		    mdt.set_vertex_normal(b, n + mdt.get_vertex_normal(b))
+		    mdt.set_vertex_normal(c, n + mdt.get_vertex_normal(c))
+
+    	# Run through vertices one last time to normalize normals and
+	    # set color to normal
+	    for i in range(mdt.get_vertex_count()):
+		    var v = mdt.get_vertex_normal(i).normalized()
+		    mdt.set_vertex_normal(i, v)
+		    mdt.set_vertex_color(i, Color(v.x, v.y, v.z))
+
+	    mesh.surface_remove(0)
+	    mdt.commit_to_surface(mesh)
+

+ 101 - 0
tutorials/content/procedural_geometry/surfacetool.rst

@@ -0,0 +1,101 @@
+.. _doc_surfacetool:
+
+Using the SurfaceTool
+=====================
+
+The :ref:`SurfaceTool <doc_surfacetool>` provides a useful interface for constructing geometry.
+The interface is similar to the :ref:`ImmediateGeometry <class_immediategeometry>` node. You 
+set each per-vertex attribute (e.g. normal, uv, color) and then when you add a vertex it 
+captures the attributes. 
+
+The SurfaceTool also provides some useful helper functions like ``index()`` and ``generate_normals()``.
+
+Attributes are added before each vertex is added:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+    
+    st.add_normal() # overwritten by normal below
+    st.add_normal() # added to next vertex
+    st.add_color() # added to next vertex
+    st.add_vertex() # captures normal and color above
+    st.add_normal() # normal never added to a vertex
+
+When finished generating your geometry with the :ref:`SurfaceTool <class_surfacetool>`
+call ``commit()`` to finished generating the mesh. If an :ref:`ArrayMesh <class_ArrayMesh>` is passed
+to ``commit()`` then it appends a new surface to the end of the ArrayMesh. While if nothing is passed
+in, ``commit()`` returns an ArrayMesh.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+  st.commit(mesh)
+  #or 
+  var mesh = st.commit()
+
+Code creates a triangle with indices
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    var st = SurfaceTool.new()
+    
+    st.begin(Mesh.PRIMITIVE_TRIANGLES)
+
+    # Prepare attributes for add_vertex.
+    st.add_normal(Vector3(0, 0, 1)
+    st.add_uv(Vector2(0, 0))
+    # Call last for each vertex, adds the above attributes.
+    st.add_vertex(Vector3(-1, -1, 0))
+
+    st.add_normal(Vector3(0, 0, 1))
+    st.add_uv(Vector2(0, 1))
+    st.add_vertex(Vector3(-1, 1, 0))
+
+    st.add_normal(Vector3(0, 0, 1))
+    st.add_uv(Vector2(1, 1))
+    st.add_vertex(Vector3(1, 1, 0))
+
+    # Commit to a mesh.
+    var mesh = st.commit()
+
+You can optionally add an index array, either by calling ``add_index()`` and adding
+vertices to the index array or by calling ``index()`` which shrinks the vertex array
+to remove duplicate vertices. 
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+  # creates a quad from four corner vertices
+  # add_index does not need to be called before add_vertex
+  st.add_index(0)
+  st.add_index(1)
+  st.add_index(2)
+
+  st.add_index(1)
+  st.add_index(3)
+  st.add_index(2)
+
+  # or alternatively
+  st.index()
+
+Similarly, if you have an index array, but you want each vertex to be unique (e.g. because
+you want to use unique normals or colors per face instead of per-vertex), you can call ``deindex()``.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+  st.deindex()
+
+If you don't add custom normals yourself, you can add them using ``generate_normals()``. The same goes for tangents.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    st.generate_normals()
+    st.generate_tangents()
+
+By default, when generating normals, they will be calculated on a per-face basis. If you want
+smooth vertex normals, when adding vertices, call ``add_smooth_group()``. ``add_smooth_group()``
+needs to be called while building the geometry, e.g. before the call to ``add_vertex()``
+(if non-indexed) or ``add_index()`` (if indexed).