diff --git a/README.md b/README.md
index 734c277..b005037 100644
--- a/README.md
+++ b/README.md
@@ -188,10 +188,11 @@ Authors
 -------
 
   * [Adam Griffiths](https://github.com/adamlwgriffiths/).
-  * [Chris Bates](https://github.com/chrsbats)
-  * [Jakub Stasiak](https://github.com/jstasiak/).
+  * [Chris Bates](https://github.com/chrsbats/).
+  * [Jakub Stasiak](https://github.com/jstasiak/).
   * [Bogdan Teleaga](https://github.com/bogdanteleaga/).
-  * [Szabolcs Dombi](https://github.com/cprogrammer1994)
+  * [Szabolcs Dombi](https://github.com/cprogrammer1994/).
+  * [Korijn van Golen](https://github.com/Korijn/).
 
 Contributions are welcome.
 
diff --git a/docs/source/info_contributing.rst b/docs/source/info_contributing.rst
index eaf4d9a..136e285 100644
--- a/docs/source/info_contributing.rst
+++ b/docs/source/info_contributing.rst
@@ -25,5 +25,6 @@ Developers and contributors include:
 
     * `Adam Griffiths <https://github.com/adamlwgriffiths/>`_
     * `Jakub Stasiak <https://github.com/jstasiak/>`_
+    * `Korijn van Golen <https://github.com/Korijn/>`_
 
 Is your name left out? Post an issue in `Pyrr's bug tracker <https://github.com/adamlwgriffiths/Pyrr/issues/>`_ =)
diff --git a/pyrr/vector3.py b/pyrr/vector3.py
index 198792e..7e52c84 100644
--- a/pyrr/vector3.py
+++ b/pyrr/vector3.py
@@ -89,9 +89,43 @@ def generate_normals(v1, v2, v3, normalize_result=True):
     b = v3 - v2
     n = cross(b, a)
     if normalize_result:
-        normalize(n)
+        n = normalize(n)
     return n
 
+def generate_vertex_normals(vertices, index, normalize_result=True):
+    """Generates a normal vector for each vertex.
+
+    The result is a normalized vector.
+
+    The index array should list the faces by indexing into the
+    vertices array. It is assumed the ordering in index is
+    counter-clockwise.
+
+    The vertices and index arrays are Nd arrays and must be 2d,
+    where the final axis is of size 3.
+
+    An example::
+        >>> vertices = numpy.array( [ [ 1.0, 0.0, 0.0 ], [ 0.0, 0.0, 0.0 ], [ 0.0, 1.0, 0.0 ] ] )
+        >>> index = numpy.array( [ [ 0, 2, 1 ] ] )
+        >>> vector.generate_vertex_normals( vertices, index )
+        array([[ 0.,  0., 1.], [ 0.,  0., 1.], [ 0.,  0., 1.]])
+
+    :param numpy.array vertices: an 2d array with the final dimension
+        being size 3. (a vector)
+    :param numpy.array index: an Nd array with the final dimension
+        being size 3. (a vector)
+    :param boolean normalize_result: Specifies if the result should
+        be normalized before being returned.
+    """
+    v1, v2, v3 = np.rollaxis(vertices[index], axis=-2)
+    face_normals = generate_normals(v1, v2, v3, normalize_result=False)
+    vertex_normals = np.zeros_like(vertices)
+    for i in range(3):
+        np.add.at(vertex_normals, index[..., i], face_normals)
+    if normalize_result:
+        vertex_normals = normalize(vertex_normals)
+    return vertex_normals
+
 
 class index:
     #: The index of the X value within the vector

generate_normals for vertices instead of faces
Your utility `pyrr.vector3.generate_normals` works wonders for generating face normals. However, for constructing vertex normals from those face normals, I needed to write some additional code.

```python
def generate_vertex_normals(vertices, index):
    face_normals = pyrr.vector3.generate_normals(*np.rollaxis(vertices[index], axis=1))
    vertex_normals = np.zeros_like(vertices)
    for i in range(index.shape[-1]):
        np.add.at(vertex_normals, index[:, i], face_normals)
    return pyrr.vector3.normalize(vertex_normals)
```

Is there already such functionality in the library that I may have missed? Is there an intention to add such a utility function?
diff --git a/pyrr/tests/test_vector3.py b/pyrr/tests/test_vector3.py
index cc64312..a51d489 100644
--- a/pyrr/tests/test_vector3.py
+++ b/pyrr/tests/test_vector3.py
@@ -102,6 +102,85 @@ class test_vector3(unittest.TestCase):
         ]
         np.testing.assert_almost_equal(result, expected, decimal=5)
 
+    def test_generate_normals(self):
+        vertices = np.array([
+            [2.0, 0.0, 0.0],
+            [0.0, 0.0, 0.0],
+            [0.0, 2.0, 0.0],
+            [2.0, 2.0, 0.0]
+        ])
+        index = np.array([
+            [0, 2, 1],
+            [0, 3, 2],
+        ])
+        v1, v2, v3 = np.rollaxis(vertices[index], axis=1)
+        result = vector3.generate_normals(v1, v2, v3)
+        expected = np.array([
+            [0., 0., 1.],
+            [0., 0., 1.]
+        ])
+        np.testing.assert_array_equal(result, expected)
+
+    def test_generate_normals_unnormalized(self):
+        vertices = np.array([
+            [2.0, 0.0, 0.0],
+            [0.0, 0.0, 0.0],
+            [0.0, 2.0, 0.0],
+            [2.0, 2.0, 0.0]
+        ])
+        index = np.array([
+            [0, 2, 1],
+            [0, 3, 2],
+        ])
+        v1, v2, v3 = np.rollaxis(vertices[index], axis=1)
+        result = vector3.generate_normals(v1, v2, v3, normalize_result=False)
+        expected = np.array([
+            [0., 0., 4.],
+            [0., 0., 4.]
+        ])
+        np.testing.assert_array_equal(result, expected)
+
+    def test_generate_vertex_normals(self):
+        vertices = np.array([
+            [1.0, 0.0, 0.0],
+            [0.0, 0.0, 0.0],
+            [0.0, 1.0, 0.0],
+            [1.0, 1.0, 0.0]
+        ])
+        index = np.array([
+            [0, 2, 1],
+            [0, 3, 2],
+        ])
+        result = vector3.generate_vertex_normals(vertices, index)
+        expected = np.array([
+            [0., 0., 1.],
+            [0., 0., 1.],
+            [0., 0., 1.],
+            [0., 0., 1.]
+        ])
+        np.testing.assert_array_equal(result, expected)
+    
+    def test_generate_vertex_normals_unnormalized(self):
+        vertices = np.array([
+            [1.0, 0.0, 0.0],
+            [0.0, 0.0, 0.0],
+            [0.0, 1.0, 0.0],
+            [1.0, 1.0, 0.0]
+        ])
+        index = np.array([
+            [0, 2, 1],
+            [0, 3, 2],
+        ])
+        result = vector3.generate_vertex_normals(
+            vertices, index, normalize_result=False)
+        expected = np.array([
+            [0., 0., 2.],
+            [0., 0., 1.],
+            [0., 0., 2.],
+            [0., 0., 1.]
+        ])
+        np.testing.assert_array_equal(result, expected)
+
     def test_squared_length_single_vector(self):
         result = vector3.squared_length([1.,1.,1.])
         np.testing.assert_almost_equal(result, 3., decimal=5)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
iniconfig==2.1.0
multipledispatch==1.0.0
numpy==2.0.2
packaging==24.2
pluggy==1.5.0
-e git+https://github.com/adamlwgriffiths/Pyrr.git@ae9903fa3365d99fae588779df9e13d23fc3fc7d#egg=pyrr
pytest==8.3.5
tomli==2.2.1

name: Pyrr
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - multipledispatch==1.0.0
      - numpy==2.0.2
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - tomli==2.2.1
prefix: /opt/conda/envs/Pyrr

pyrr/tests/test_vector3.py::test_vector3::test_generate_normals pyrr/tests/test_vector3.py::test_vector3::test_generate_vertex_normals pyrr/tests/test_vector3.py::test_vector3::test_generate_vertex_normals_unnormalized
pyrr/tests/test_vector3.py::test_vector3::test_create pyrr/tests/test_vector3.py::test_vector3::test_create_from_matrix44_translation pyrr/tests/test_vector3.py::test_vector3::test_create_unit_length_x pyrr/tests/test_vector3.py::test_vector3::test_create_unit_length_y pyrr/tests/test_vector3.py::test_vector3::test_create_unit_length_z
pyrr/tests/test_vector3.py::test_vector3::test_create_from_matrix44_translation_dtype_matches pyrr/tests/test_vector3.py::test_vector3::test_create_from_vector4 pyrr/tests/test_vector3.py::test_vector3::test_create_list pyrr/tests/test_vector3.py::test_vector3::test_create_unit_length_x_dtype pyrr/tests/test_vector3.py::test_vector3::test_create_unit_length_y_dtype pyrr/tests/test_vector3.py::test_vector3::test_create_unit_length_z_dtype pyrr/tests/test_vector3.py::test_vector3::test_create_values pyrr/tests/test_vector3.py::test_vector3::test_cross_batch pyrr/tests/test_vector3.py::test_vector3::test_cross_coincident pyrr/tests/test_vector3.py::test_vector3::test_cross_single_vector pyrr/tests/test_vector3.py::test_vector3::test_dot_adjacent pyrr/tests/test_vector3.py::test_vector3::test_dot_angle pyrr/tests/test_vector3.py::test_vector3::test_dot_batch pyrr/tests/test_vector3.py::test_vector3::test_dot_parallel pyrr/tests/test_vector3.py::test_vector3::test_generate_normals_unnormalized pyrr/tests/test_vector3.py::test_vector3::test_import pyrr/tests/test_vector3.py::test_vector3::test_interoplation pyrr/tests/test_vector3.py::test_vector3::test_length pyrr/tests/test_vector3.py::test_vector3::test_length_batch pyrr/tests/test_vector3.py::test_vector3::test_normalize_batch pyrr/tests/test_vector3.py::test_vector3::test_normalize_single_vector pyrr/tests/test_vector3.py::test_vector3::test_set_length pyrr/tests/test_vector3.py::test_vector3::test_set_length_batch_vector pyrr/tests/test_vector3.py::test_vector3::test_squared_length_batch pyrr/tests/test_vector3.py::test_vector3::test_squared_length_single_vector
docs/source/info_contributing.rst README.md pyrr/vector3.py
docs/source/info_contributing.rst README.md pyrr/vector3.py