diff --git a/lib/render_vispy/model3d.py b/lib/render_vispy/model3d.py
index b4752a411ac8eb424e201b8ad261bc39c115825a..cf43e1e130e76cef0ab328450a18f9dd04041bb9 100644
--- a/lib/render_vispy/model3d.py
+++ b/lib/render_vispy/model3d.py
@@ -3,7 +3,6 @@ import os
 import os.path as osp
 
 import cv2
-import fastfunc
 import mmcv
 import numpy as np
 import pyassimp
@@ -30,6 +29,12 @@ support model_loadfn to load objects
 """
 
 
+def _fast_add_at(target, idx, vals):
+    # https://github.com/ml31415/numpy-groupies/issues/24
+    # https://github.com/numpy/numpy/issues/5922
+    return target + np.bincount(idx, weights=vals, minlength=target.shape[0])
+
+
 class Model3D:
     """"""
 
@@ -333,11 +338,11 @@ class Model3D:
         num_neighbors = np.zeros(len(mesh.node_coords), dtype=int)
 
         idx = mesh.edges["nodes"]
-        fastfunc.add.at(num_neighbors, idx, np.ones(idx.shape, dtype=int))
+        num_neighbors = _fast_add_at(num_neighbors, idx, np.ones(idx.shape, dtype=int))
 
         new_points = np.zeros(mesh.node_coords.shape)
-        fastfunc.add.at(new_points, idx[:, 0], mesh.node_coords[idx[:, 1]])
-        fastfunc.add.at(new_points, idx[:, 1], mesh.node_coords[idx[:, 0]])
+        new_points = _fast_add_at(new_points, idx[:, 0], mesh.node_coords[idx[:, 1]])
+        new_points = _fast_add_at(new_points, idx[:, 1], mesh.node_coords[idx[:, 0]])
 
         new_points /= num_neighbors[:, None]
         idx = mesh.is_boundary_node
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index 95dd7a75d2d4a1ffab932f2869072fb9935436c9..e8382d75a57f5d70bde0cd904f3cc072c4becbfb 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -9,7 +9,6 @@ ninja
 black
 docformatter
 setproctitle
-fastfunc
 meshplex
 OpenEXR
 vispy>=0.6.4