/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/** \file
 * \ingroup bke
 */

#include "MEM_guardedalloc.h"

#include <math.h>
#include <stdio.h>

#include "BLI_blenlib.h"
#include "BLI_kdtree.h"
#include "BLI_math.h"
#include "BLI_string_utils.h"
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"

#include "BLT_translation.h"

#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_collection_types.h"
#include "DNA_constraint_types.h"
#include "DNA_dynamicpaint_types.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_force_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_texture_types.h"

#include "BKE_armature.h"
#include "BKE_bvhutils.h" /* bvh tree */
#include "BKE_collection.h"
#include "BKE_collision.h"
#include "BKE_colorband.h"
#include "BKE_constraint.h"
#include "BKE_customdata.h"
#include "BKE_deform.h"
#include "BKE_dynamicpaint.h"
#include "BKE_effect.h"
#include "BKE_image.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_particle.h"
#include "BKE_pointcache.h"
#include "BKE_scene.h"

#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"

/* for image output */
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"

#include "RE_texture.h"

#include "atomic_ops.h"

#include "CLG_log.h"

/* could enable at some point but for now there are far too many conversions */
#ifdef __GNUC__
//#  pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif

static CLG_LogRef LOG = {"bke.dynamicpaint"};

/* precalculated gaussian factors for 5x super sampling */
static const float gaussianFactors[5] = {
    0.996849f,
    0.596145f,
    0.596145f,
    0.596145f,
    0.524141f,
};
static const float gaussianTotal = 3.309425f;

/* UV Image neighboring pixel table x and y list */
static int neighX[8] = {1, 1, 0, -1, -1, -1, 0, 1};
static int neighY[8] = {0, 1, 1, 1, 0, -1, -1, -1};

/* Neighbor x/y list that prioritizes grid directions over diagonals */
static int neighStraightX[8] = {1, 0, -1, 0, 1, -1, -1, 1};
static int neighStraightY[8] = {0, 1, 0, -1, 1, 1, -1, -1};

/* subframe_updateObject() flags */
#define SUBFRAME_RECURSION 5
/* surface_getBrushFlags() return vals */
#define BRUSH_USES_VELOCITY (1 << 0)
/* Brush mesh ray-cast status. */
#define HIT_VOLUME 1
#define HIT_PROXIMITY 2
/* dynamicPaint_findNeighborPixel() return codes */
#define NOT_FOUND -1
#define ON_MESH_EDGE -2
#define OUT_OF_TEXTURE -3
/* paint effect default movement per frame in global units */
#define EFF_MOVEMENT_PER_FRAME 0.05f
/* initial wave time factor */
#define WAVE_TIME_FAC (1.0f / 24.0f)
#define CANVAS_REL_SIZE 5.0f
/* drying limits */
#define MIN_WETNESS 0.001f
#define MAX_WETNESS 5.0f

/* dissolve inline function */
BLI_INLINE void value_dissolve(float *r_value,
                               const float time,
                               const float scale,
                               const bool is_log)
{
  *r_value = (is_log) ? (*r_value) * (powf(MIN_WETNESS, 1.0f / (1.2f * time / scale))) :
                        (*r_value) - 1.0f / time * scale;
}

/***************************** Internal Structs ***************************/

typedef struct Bounds2D {
  float min[2], max[2];
} Bounds2D;

typedef struct Bounds3D {
  float min[3], max[3];
  bool valid;
} Bounds3D;

typedef struct VolumeGrid {
  int dim[3];
  /** whole grid bounds */
  Bounds3D grid_bounds;

  /** (x*y*z) precalculated grid cell bounds */
  Bounds3D *bounds;
  /** (x*y*z) t_index begin id */
  int *s_pos;
  /** (x*y*z) number of t_index points */
  int *s_num;
  /** actual surface point index, access: (s_pos + s_num) */
  int *t_index;

  int *temp_t_index;
} VolumeGrid;

typedef struct Vec3f {
  float v[3];
} Vec3f;

typedef struct BakeAdjPoint {
  /** vector pointing towards this neighbor */
  float dir[3];
  /** distance to */
  float dist;
} BakeAdjPoint;

/** Surface data used while processing a frame */
typedef struct PaintBakeNormal {
  /** current pixel world-space inverted normal */
  float invNorm[3];
  /** normal directional scale for displace mapping */
  float normal_scale;
} PaintBakeNormal;

/** Temp surface data used to process a frame */
typedef struct PaintBakeData {
  /* point space data */
  PaintBakeNormal *bNormal;
  /** index to start reading point sample realCoord */
  int *s_pos;
  /** num of realCoord samples */
  int *s_num;
  /** current pixel center world-space coordinates for each sample ordered as (s_pos + s_num) */
  Vec3f *realCoord;
  Bounds3D mesh_bounds;
  float dim[3];

  /* adjacency info */
  /** current global neighbor distances and directions, if required */
  BakeAdjPoint *bNeighs;
  double average_dist;
  /* space partitioning */
  /** space partitioning grid to optimize brush checks */
  VolumeGrid *grid;

  /* velocity and movement */
  /** speed vector in global space movement per frame, if required */
  Vec3f *velocity;
  Vec3f *prev_velocity;
  /** special temp data for post-p velocity based brushes like smudge
   * 3 float dir vec + 1 float str */
  float *brush_velocity;
  /** copy of previous frame vertices. used to observe surface movement. */
  MVert *prev_verts;
  /** Previous frame object matrix. */
  float prev_obmat[4][4];
  /** flag to check if surface was cleared/reset -> have to redo velocity etc. */
  int clear;
} PaintBakeData;

/** UV Image sequence format point */
typedef struct PaintUVPoint {
  /* Pixel / mesh data */
  /** tri index on domain derived mesh */
  unsigned int tri_index;
  unsigned int pixel_index;
  /* vertex indexes */
  unsigned int v1, v2, v3;

  /** If this pixel isn't uv mapped to any face, but its neighboring pixel is. */
  unsigned int neighbor_pixel;
} PaintUVPoint;

typedef struct ImgSeqFormatData {
  PaintUVPoint *uv_p;
  Vec3f *barycentricWeights; /* b-weights for all pixel samples */
} ImgSeqFormatData;

/* adjacency data flags */
#define ADJ_ON_MESH_EDGE (1 << 0)
#define ADJ_BORDER_PIXEL (1 << 1)

typedef struct PaintAdjData {
  /** Array of neighboring point indexes, for single sample use (n_index + neigh_num). */
  int *n_target;
  /** Index to start reading n_target for each point. */
  int *n_index;
  /** Number of neighbors for each point. */
  int *n_num;
  /** Vertex adjacency flags. */
  int *flags;
  /** Size of n_target. */
  int total_targets;
  /** Indices of border pixels (only for texture paint). */
  int *border;
  /** Size of border. */
  int total_border;
} PaintAdjData;

/************************* Runtime evaluation store ***************************/

void dynamicPaint_Modifier_free_runtime(DynamicPaintRuntime *runtime_data)
{
  if (runtime_data == NULL) {
    return;
  }
  if (runtime_data->canvas_mesh) {
    BKE_id_free(NULL, runtime_data->canvas_mesh);
  }
  if (runtime_data->brush_mesh) {
    BKE_id_free(NULL, runtime_data->brush_mesh);
  }
  MEM_freeN(runtime_data);
}

static DynamicPaintRuntime *dynamicPaint_Modifier_runtime_ensure(DynamicPaintModifierData *pmd)
{
  if (pmd->modifier.runtime == NULL) {
    pmd->modifier.runtime = MEM_callocN(sizeof(DynamicPaintRuntime), "dynamic paint runtime");
  }
  return (DynamicPaintRuntime *)pmd->modifier.runtime;
}

static Mesh *dynamicPaint_canvas_mesh_get(DynamicPaintCanvasSettings *canvas)
{
  if (canvas->pmd->modifier.runtime == NULL) {
    return NULL;
  }
  DynamicPaintRuntime *runtime_data = (DynamicPaintRuntime *)canvas->pmd->modifier.runtime;
  return runtime_data->canvas_mesh;
}

static Mesh *dynamicPaint_brush_mesh_get(DynamicPaintBrushSettings *brush)
{
  if (brush->pmd->modifier.runtime == NULL) {
    return NULL;
  }
  DynamicPaintRuntime *runtime_data = (DynamicPaintRuntime *)brush->pmd->modifier.runtime;
  return runtime_data->brush_mesh;
}

/***************************** General Utils ******************************/

/* Set canvas error string to display at the bake report */
static bool setError(DynamicPaintCanvasSettings *canvas, const char *string)
{
  /* Add error to canvas ui info label */
  BLI_strncpy(canvas->error, string, sizeof(canvas->error));
  CLOG_STR_ERROR(&LOG, string);
  return false;
}

/* Get number of surface points for cached types */
static int dynamicPaint_surfaceNumOfPoints(DynamicPaintSurface *surface)
{
  if (surface->format == MOD_DPAINT_SURFACE_F_PTEX) {
    return 0; /* Not supported at the moment. */
  }
  if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
    const Mesh *canvas_mesh = dynamicPaint_canvas_mesh_get(surface->canvas);
    return (canvas_mesh) ? canvas_mesh->totvert : 0;
  }

  return 0;
}

DynamicPaintSurface *get_activeSurface(DynamicPaintCanvasSettings *canvas)
{
  return BLI_findlink(&canvas->surfaces, canvas->active_sur);
}

bool dynamicPaint_outputLayerExists(struct DynamicPaintSurface *surface, Object *ob, int output)
{
  const char *name;

  if (output == 0) {
    name = surface->output_name;
  }
  else if (output == 1) {
    name = surface->output_name2;
  }
  else {
    return false;
  }

  if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      Mesh *me = ob->data;
      return (CustomData_get_named_layer_index(&me->ldata, CD_MLOOPCOL, name) != -1);
    }
    if (surface->type == MOD_DPAINT_SURFACE_T_WEIGHT) {
      return (BKE_object_defgroup_name_index(ob, name) != -1);
    }
  }

  return false;
}

static bool surface_duplicateOutputExists(void *arg, const char *name)
{
  DynamicPaintSurface *t_surface = arg;
  DynamicPaintSurface *surface = t_surface->canvas->surfaces.first;

  for (; surface; surface = surface->next) {
    if (surface != t_surface && surface->type == t_surface->type &&
        surface->format == t_surface->format) {
      if ((surface->output_name[0] != '\0' && !BLI_path_cmp(name, surface->output_name)) ||
          (surface->output_name2[0] != '\0' && !BLI_path_cmp(name, surface->output_name2))) {
        return true;
      }
    }
  }
  return false;
}

static void surface_setUniqueOutputName(DynamicPaintSurface *surface, char *basename, int output)
{
  char name[64];
  BLI_strncpy(name, basename, sizeof(name)); /* in case basename is surface->name use a copy */
  if (output == 0) {
    BLI_uniquename_cb(surface_duplicateOutputExists,
                      surface,
                      name,
                      '.',
                      surface->output_name,
                      sizeof(surface->output_name));
  }
  else if (output == 1) {
    BLI_uniquename_cb(surface_duplicateOutputExists,
                      surface,
                      name,
                      '.',
                      surface->output_name2,
                      sizeof(surface->output_name2));
  }
}

static bool surface_duplicateNameExists(void *arg, const char *name)
{
  DynamicPaintSurface *t_surface = arg;
  DynamicPaintSurface *surface = t_surface->canvas->surfaces.first;

  for (; surface; surface = surface->next) {
    if (surface != t_surface && STREQ(name, surface->name)) {
      return true;
    }
  }
  return false;
}

void dynamicPaintSurface_setUniqueName(DynamicPaintSurface *surface, const char *basename)
{
  char name[64];
  BLI_strncpy(name, basename, sizeof(name)); /* in case basename is surface->name use a copy */
  BLI_uniquename_cb(
      surface_duplicateNameExists, surface, name, '.', surface->name, sizeof(surface->name));
}

void dynamicPaintSurface_updateType(struct DynamicPaintSurface *surface)
{
  if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
    surface->output_name[0] = '\0';
    surface->output_name2[0] = '\0';
    surface->flags |= MOD_DPAINT_ANTIALIAS;
    surface->depth_clamp = 1.0f;
  }
  else {
    strcpy(surface->output_name, "dp_");
    BLI_strncpy(surface->output_name2, surface->output_name, sizeof(surface->output_name2));
    surface->flags &= ~MOD_DPAINT_ANTIALIAS;
    surface->depth_clamp = 0.0f;
  }

  if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
    strcat(surface->output_name, "paintmap");
    strcat(surface->output_name2, "wetmap");
    surface_setUniqueOutputName(surface, surface->output_name2, 1);
  }
  else if (surface->type == MOD_DPAINT_SURFACE_T_DISPLACE) {
    strcat(surface->output_name, "displace");
  }
  else if (surface->type == MOD_DPAINT_SURFACE_T_WEIGHT) {
    strcat(surface->output_name, "weight");
  }
  else if (surface->type == MOD_DPAINT_SURFACE_T_WAVE) {
    strcat(surface->output_name, "wave");
  }

  surface_setUniqueOutputName(surface, surface->output_name, 0);
}

static int surface_totalSamples(DynamicPaintSurface *surface)
{
  if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ && surface->flags & MOD_DPAINT_ANTIALIAS) {
    return (surface->data->total_points * 5);
  }
  if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX && surface->flags & MOD_DPAINT_ANTIALIAS &&
      surface->data->adj_data) {
    return (surface->data->total_points + surface->data->adj_data->total_targets);
  }

  return surface->data->total_points;
}

static void blendColors(const float t_color[3],
                        const float t_alpha,
                        const float s_color[3],
                        const float s_alpha,
                        float result[4])
{
  /* Same thing as BLI's blend_color_mix_float(), but for non-premultiplied alpha. */
  float i_alpha = 1.0f - s_alpha;
  float f_alpha = t_alpha * i_alpha + s_alpha;

  /* blend colors */
  if (f_alpha) {
    for (int i = 0; i < 3; i++) {
      result[i] = (t_color[i] * t_alpha * i_alpha + s_color[i] * s_alpha) / f_alpha;
    }
  }
  else {
    copy_v3_v3(result, t_color);
  }
  /* return final alpha */
  result[3] = f_alpha;
}

/* Mix two alpha weighed colors by a defined ratio. output is saved at a_color */
static float mixColors(
    float a_color[3], float a_weight, const float b_color[3], float b_weight, float ratio)
{
  float weight_ratio, factor;
  if (b_weight) {
    /* if first value has no weight just use b_color */
    if (!a_weight) {
      copy_v3_v3(a_color, b_color);
      return b_weight * ratio;
    }
    weight_ratio = b_weight / (a_weight + b_weight);
  }
  else {
    return a_weight * (1.0f - ratio);
  }

  /* calculate final interpolation factor */
  if (ratio <= 0.5f) {
    factor = weight_ratio * (ratio * 2.0f);
  }
  else {
    ratio = (ratio * 2.0f - 1.0f);
    factor = weight_ratio * (1.0f - ratio) + ratio;
  }
  /* mix final color */
  interp_v3_v3v3(a_color, a_color, b_color, factor);
  return (1.0f - factor) * a_weight + factor * b_weight;
}

static void scene_setSubframe(Scene *scene, float subframe)
{
  /* dynamic paint subframes must be done on previous frame */
  scene->r.cfra -= 1;
  scene->r.subframe = subframe;
}

static int surface_getBrushFlags(DynamicPaintSurface *surface, Depsgraph *depsgraph)
{
  unsigned int numobjects;
  Object **objects = BKE_collision_objects_create(
      depsgraph, NULL, surface->brush_group, &numobjects, eModifierType_DynamicPaint);

  int flags = 0;

  for (int i = 0; i < numobjects; i++) {
    Object *brushObj = objects[i];

    ModifierData *md = BKE_modifiers_findby_type(brushObj, eModifierType_DynamicPaint);
    if (md && md->mode & (eModifierMode_Realtime | eModifierMode_Render)) {
      DynamicPaintModifierData *pmd2 = (DynamicPaintModifierData *)md;

      if (pmd2->brush) {
        DynamicPaintBrushSettings *brush = pmd2->brush;

        if (brush->flags & MOD_DPAINT_USES_VELOCITY) {
          flags |= BRUSH_USES_VELOCITY;
        }
      }
    }
  }

  BKE_collision_objects_free(objects);

  return flags;
}

/* check whether two bounds intersect */
static bool boundsIntersect(Bounds3D *b1, Bounds3D *b2)
{
  if (!b1->valid || !b2->valid) {
    return false;
  }
  for (int i = 2; i--;) {
    if (!(b1->min[i] <= b2->max[i] && b1->max[i] >= b2->min[i])) {
      return false;
    }
  }
  return true;
}

/* check whether two bounds intersect inside defined proximity */
static bool boundsIntersectDist(Bounds3D *b1, Bounds3D *b2, const float dist)
{
  if (!b1->valid || !b2->valid) {
    return false;
  }
  for (int i = 2; i--;) {
    if (!(b1->min[i] <= (b2->max[i] + dist) && b1->max[i] >= (b2->min[i] - dist))) {
      return false;
    }
  }
  return true;
}

/* check whether bounds intersects a point with given radius */
static bool boundIntersectPoint(Bounds3D *b, const float point[3], const float radius)
{
  if (!b->valid) {
    return false;
  }
  for (int i = 2; i--;) {
    if (!(b->min[i] <= (point[i] + radius) && b->max[i] >= (point[i] - radius))) {
      return false;
    }
  }
  return true;
}

/* expand bounds by a new point */
static void boundInsert(Bounds3D *b, const float point[3])
{
  if (!b->valid) {
    copy_v3_v3(b->min, point);
    copy_v3_v3(b->max, point);
    b->valid = true;
    return;
  }

  minmax_v3v3_v3(b->min, b->max, point);
}

static float getSurfaceDimension(PaintSurfaceData *sData)
{
  Bounds3D *mb = &sData->bData->mesh_bounds;
  return max_fff((mb->max[0] - mb->min[0]), (mb->max[1] - mb->min[1]), (mb->max[2] - mb->min[2]));
}

static void freeGrid(PaintSurfaceData *data)
{
  PaintBakeData *bData = data->bData;
  VolumeGrid *grid = bData->grid;

  if (grid->bounds) {
    MEM_freeN(grid->bounds);
  }
  if (grid->s_pos) {
    MEM_freeN(grid->s_pos);
  }
  if (grid->s_num) {
    MEM_freeN(grid->s_num);
  }
  if (grid->t_index) {
    MEM_freeN(grid->t_index);
  }

  MEM_freeN(bData->grid);
  bData->grid = NULL;
}

static void grid_bound_insert_cb_ex(void *__restrict userdata,
                                    const int i,
                                    const TaskParallelTLS *__restrict tls)
{
  PaintBakeData *bData = userdata;

  Bounds3D *grid_bound = tls->userdata_chunk;

  boundInsert(grid_bound, bData->realCoord[bData->s_pos[i]].v);
}

static void grid_bound_insert_reduce(const void *__restrict UNUSED(userdata),
                                     void *__restrict chunk_join,
                                     void *__restrict chunk)
{
  Bounds3D *join = chunk_join;
  Bounds3D *grid_bound = chunk;

  boundInsert(join, grid_bound->min);
  boundInsert(join, grid_bound->max);
}

static void grid_cell_points_cb_ex(void *__restrict userdata,
                                   const int i,
                                   const TaskParallelTLS *__restrict tls)
{
  PaintBakeData *bData = userdata;
  VolumeGrid *grid = bData->grid;
  int *temp_t_index = grid->temp_t_index;
  int *s_num = tls->userdata_chunk;

  int co[3];

  for (int j = 3; j--;) {
    co[j] = (int)floorf((bData->realCoord[bData->s_pos[i]].v[j] - grid->grid_bounds.min[j]) /
                        bData->dim[j] * grid->dim[j]);
    CLAMP(co[j], 0, grid->dim[j] - 1);
  }

  temp_t_index[i] = co[0] + co[1] * grid->dim[0] + co[2] * grid->dim[0] * grid->dim[1];
  s_num[temp_t_index[i]]++;
}

static void grid_cell_points_reduce(const void *__restrict userdata,
                                    void *__restrict chunk_join,
                                    void *__restrict chunk)
{
  const PaintBakeData *bData = userdata;
  const VolumeGrid *grid = bData->grid;
  const int grid_cells = grid->dim[0] * grid->dim[1] * grid->dim[2];

  int *join_s_num = chunk_join;
  int *s_num = chunk;

  /* calculate grid indexes */
  for (int i = 0; i < grid_cells; i++) {
    join_s_num[i] += s_num[i];
  }
}

static void grid_cell_bounds_cb(void *__restrict userdata,
                                const int x,
                                const TaskParallelTLS *__restrict UNUSED(tls))
{
  PaintBakeData *bData = userdata;
  VolumeGrid *grid = bData->grid;
  float *dim = bData->dim;
  int *grid_dim = grid->dim;

  for (int y = 0; y < grid_dim[1]; y++) {
    for (int z = 0; z < grid_dim[2]; z++) {
      const int b_index = x + y * grid_dim[0] + z * grid_dim[0] * grid_dim[1];
      /* set bounds */
      for (int j = 3; j--;) {
        const int s = (j == 0) ? x : ((j == 1) ? y : z);
        grid->bounds[b_index].min[j] = grid->grid_bounds.min[j] + dim[j] / grid_dim[j] * s;
        grid->bounds[b_index].max[j] = grid->grid_bounds.min[j] + dim[j] / grid_dim[j] * (s + 1);
      }
      grid->bounds[b_index].valid = true;
    }
  }
}

static void surfaceGenerateGrid(struct DynamicPaintSurface *surface)
{
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  VolumeGrid *grid;
  int grid_cells, axis = 3;
  int *temp_t_index = NULL;
  int *temp_s_num = NULL;

  if (bData->grid) {
    freeGrid(sData);
  }

  bData->grid = MEM_callocN(sizeof(VolumeGrid), "Surface Grid");
  grid = bData->grid;

  {
    int i, error = 0;
    float dim_factor, volume, dim[3];
    float td[3];
    float min_dim;

    /* calculate canvas dimensions */
    /* Important to init correctly our ref grid_bound... */
    boundInsert(&grid->grid_bounds, bData->realCoord[bData->s_pos[0]].v);
    {
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (sData->total_points > 1000);
      settings.userdata_chunk = &grid->grid_bounds;
      settings.userdata_chunk_size = sizeof(grid->grid_bounds);
      settings.func_reduce = grid_bound_insert_reduce;
      BLI_task_parallel_range(0, sData->total_points, bData, grid_bound_insert_cb_ex, &settings);
    }
    /* get dimensions */
    sub_v3_v3v3(dim, grid->grid_bounds.max, grid->grid_bounds.min);
    copy_v3_v3(td, dim);
    copy_v3_v3(bData->dim, dim);
    min_dim = max_fff(td[0], td[1], td[2]) / 1000.0f;

    /* deactivate zero axes */
    for (i = 0; i < 3; i++) {
      if (td[i] < min_dim) {
        td[i] = 1.0f;
        axis--;
      }
    }

    if (axis == 0 || max_fff(td[0], td[1], td[2]) < 0.0001f) {
      MEM_freeN(bData->grid);
      bData->grid = NULL;
      return;
    }

    /* now calculate grid volume/area/width depending on num of active axis */
    volume = td[0] * td[1] * td[2];

    /* determine final grid size by trying to fit average 10.000 points per grid cell */
    dim_factor = (float)pow((double)volume / ((double)sData->total_points / 10000.0),
                            1.0 / (double)axis);

    /* define final grid size using dim_factor, use min 3 for active axes */
    for (i = 0; i < 3; i++) {
      grid->dim[i] = (int)floor(td[i] / dim_factor);
      CLAMP(grid->dim[i], (dim[i] >= min_dim) ? 3 : 1, 100);
    }
    grid_cells = grid->dim[0] * grid->dim[1] * grid->dim[2];

    /* allocate memory for grids */
    grid->bounds = MEM_callocN(sizeof(Bounds3D) * grid_cells, "Surface Grid Bounds");
    grid->s_pos = MEM_callocN(sizeof(int) * grid_cells, "Surface Grid Position");

    grid->s_num = MEM_callocN(sizeof(int) * grid_cells, "Surface Grid Points");
    temp_s_num = MEM_callocN(sizeof(int) * grid_cells, "Temp Surface Grid Points");
    grid->t_index = MEM_callocN(sizeof(int) * sData->total_points, "Surface Grid Target Ids");
    grid->temp_t_index = temp_t_index = MEM_callocN(sizeof(int) * sData->total_points,
                                                    "Temp Surface Grid Target Ids");

    /* in case of an allocation failure abort here */
    if (!grid->bounds || !grid->s_pos || !grid->s_num || !grid->t_index || !temp_s_num ||
        !temp_t_index) {
      error = 1;
    }

    if (!error) {
      /* calculate number of points within each cell */
      {
        TaskParallelSettings settings;
        BLI_parallel_range_settings_defaults(&settings);
        settings.use_threading = (sData->total_points > 1000);
        settings.userdata_chunk = grid->s_num;
        settings.userdata_chunk_size = sizeof(*grid->s_num) * grid_cells;
        settings.func_reduce = grid_cell_points_reduce;
        BLI_task_parallel_range(0, sData->total_points, bData, grid_cell_points_cb_ex, &settings);
      }

      /* calculate grid indexes (not needed for first cell, which is zero). */
      for (i = 1; i < grid_cells; i++) {
        grid->s_pos[i] = grid->s_pos[i - 1] + grid->s_num[i - 1];
      }

      /* save point indexes to final array */
      for (i = 0; i < sData->total_points; i++) {
        int pos = grid->s_pos[temp_t_index[i]] + temp_s_num[temp_t_index[i]];
        grid->t_index[pos] = i;

        temp_s_num[temp_t_index[i]]++;
      }

      /* calculate cell bounds */
      {
        TaskParallelSettings settings;
        BLI_parallel_range_settings_defaults(&settings);
        settings.use_threading = (grid_cells > 1000);
        BLI_task_parallel_range(0, grid->dim[0], bData, grid_cell_bounds_cb, &settings);
      }
    }

    if (temp_s_num) {
      MEM_freeN(temp_s_num);
    }
    MEM_SAFE_FREE(temp_t_index);

    if (error || !grid->s_num) {
      setError(surface->canvas, N_("Not enough free memory"));
      freeGrid(sData);
    }
  }
}

/***************************** Freeing data ******************************/

void dynamicPaint_freeBrush(struct DynamicPaintModifierData *pmd)
{
  if (pmd->brush) {
    if (pmd->brush->paint_ramp) {
      MEM_freeN(pmd->brush->paint_ramp);
    }
    if (pmd->brush->vel_ramp) {
      MEM_freeN(pmd->brush->vel_ramp);
    }

    MEM_freeN(pmd->brush);
    pmd->brush = NULL;
  }
}

static void dynamicPaint_freeAdjData(PaintSurfaceData *data)
{
  if (data->adj_data) {
    if (data->adj_data->n_index) {
      MEM_freeN(data->adj_data->n_index);
    }
    if (data->adj_data->n_num) {
      MEM_freeN(data->adj_data->n_num);
    }
    if (data->adj_data->n_target) {
      MEM_freeN(data->adj_data->n_target);
    }
    if (data->adj_data->flags) {
      MEM_freeN(data->adj_data->flags);
    }
    if (data->adj_data->border) {
      MEM_freeN(data->adj_data->border);
    }
    MEM_freeN(data->adj_data);
    data->adj_data = NULL;
  }
}

static void free_bakeData(PaintSurfaceData *data)
{
  PaintBakeData *bData = data->bData;
  if (bData) {
    if (bData->bNormal) {
      MEM_freeN(bData->bNormal);
    }
    if (bData->s_pos) {
      MEM_freeN(bData->s_pos);
    }
    if (bData->s_num) {
      MEM_freeN(bData->s_num);
    }
    if (bData->realCoord) {
      MEM_freeN(bData->realCoord);
    }
    if (bData->bNeighs) {
      MEM_freeN(bData->bNeighs);
    }
    if (bData->grid) {
      freeGrid(data);
    }
    if (bData->prev_verts) {
      MEM_freeN(bData->prev_verts);
    }
    if (bData->velocity) {
      MEM_freeN(bData->velocity);
    }
    if (bData->prev_velocity) {
      MEM_freeN(bData->prev_velocity);
    }

    MEM_freeN(data->bData);
    data->bData = NULL;
  }
}

/* free surface data if it's not used anymore */
static void surface_freeUnusedData(DynamicPaintSurface *surface)
{
  if (!surface->data) {
    return;
  }

  /* free bakedata if not active or surface is baked */
  if (!(surface->flags & MOD_DPAINT_ACTIVE) ||
      (surface->pointcache && surface->pointcache->flag & PTCACHE_BAKED)) {
    free_bakeData(surface->data);
  }
}

void dynamicPaint_freeSurfaceData(DynamicPaintSurface *surface)
{
  PaintSurfaceData *data = surface->data;
  if (!data) {
    return;
  }

  if (data->format_data) {
    /* format specific free */
    if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
      ImgSeqFormatData *format_data = (ImgSeqFormatData *)data->format_data;
      if (format_data->uv_p) {
        MEM_freeN(format_data->uv_p);
      }
      if (format_data->barycentricWeights) {
        MEM_freeN(format_data->barycentricWeights);
      }
    }
    MEM_freeN(data->format_data);
  }
  /* type data */
  if (data->type_data) {
    MEM_freeN(data->type_data);
  }
  dynamicPaint_freeAdjData(data);
  /* bake data */
  free_bakeData(data);

  MEM_freeN(surface->data);
  surface->data = NULL;
}

void dynamicPaint_freeSurface(const DynamicPaintModifierData *pmd, DynamicPaintSurface *surface)
{
  /* point cache */
  if ((pmd->modifier.flag & eModifierFlag_SharedCaches) == 0) {
    BKE_ptcache_free_list(&(surface->ptcaches));
  }
  surface->pointcache = NULL;

  MEM_SAFE_FREE(surface->effector_weights);

  BLI_remlink(&(surface->canvas->surfaces), surface);
  dynamicPaint_freeSurfaceData(surface);
  MEM_freeN(surface);
}

void dynamicPaint_freeCanvas(DynamicPaintModifierData *pmd)
{
  if (pmd->canvas) {
    /* Free surface data */
    DynamicPaintSurface *surface = pmd->canvas->surfaces.first;
    DynamicPaintSurface *next_surface = NULL;

    while (surface) {
      next_surface = surface->next;
      dynamicPaint_freeSurface(pmd, surface);
      surface = next_surface;
    }

    MEM_freeN(pmd->canvas);
    pmd->canvas = NULL;
  }
}

void dynamicPaint_Modifier_free(DynamicPaintModifierData *pmd)
{
  if (pmd == NULL) {
    return;
  }
  dynamicPaint_freeCanvas(pmd);
  dynamicPaint_freeBrush(pmd);
  dynamicPaint_Modifier_free_runtime(pmd->modifier.runtime);
}

/***************************** Initialize and reset ******************************/

DynamicPaintSurface *dynamicPaint_createNewSurface(DynamicPaintCanvasSettings *canvas,
                                                   Scene *scene)
{
  DynamicPaintSurface *surface = MEM_callocN(sizeof(DynamicPaintSurface), "DynamicPaintSurface");
  if (!surface) {
    return NULL;
  }

  surface->canvas = canvas;
  surface->format = MOD_DPAINT_SURFACE_F_VERTEX;
  surface->type = MOD_DPAINT_SURFACE_T_PAINT;

  /* cache */
  surface->pointcache = BKE_ptcache_add(&(surface->ptcaches));
  surface->pointcache->flag |= PTCACHE_DISK_CACHE;
  surface->pointcache->step = 1;

  /* Set initial values */
  surface->flags = MOD_DPAINT_ANTIALIAS | MOD_DPAINT_MULALPHA | MOD_DPAINT_DRY_LOG |
                   MOD_DPAINT_DISSOLVE_LOG | MOD_DPAINT_ACTIVE | MOD_DPAINT_OUT1 |
                   MOD_DPAINT_USE_DRYING;
  surface->effect = 0;
  surface->effect_ui = 1;

  surface->diss_speed = 250;
  surface->dry_speed = 500;
  surface->color_dry_threshold = 1.0f;
  surface->depth_clamp = 0.0f;
  surface->disp_factor = 1.0f;
  surface->disp_type = MOD_DPAINT_DISP_DISPLACE;
  surface->image_fileformat = MOD_DPAINT_IMGFORMAT_PNG;

  surface->influence_scale = 1.0f;
  surface->radius_scale = 1.0f;

  surface->init_color[0] = 1.0f;
  surface->init_color[1] = 1.0f;
  surface->init_color[2] = 1.0f;
  surface->init_color[3] = 1.0f;

  surface->image_resolution = 256;
  surface->substeps = 0;

  if (scene) {
    surface->start_frame = scene->r.sfra;
    surface->end_frame = scene->r.efra;
  }
  else {
    surface->start_frame = 1;
    surface->end_frame = 250;
  }

  surface->spread_speed = 1.0f;
  surface->color_spread_speed = 1.0f;
  surface->shrink_speed = 1.0f;

  surface->wave_damping = 0.04f;
  surface->wave_speed = 1.0f;
  surface->wave_timescale = 1.0f;
  surface->wave_spring = 0.20f;
  surface->wave_smoothness = 1.0f;

  BKE_modifier_path_init(
      surface->image_output_path, sizeof(surface->image_output_path), "cache_dynamicpaint");

  /* Using ID_BRUSH i18n context, as we have no physics/dpaint one for now... */
  dynamicPaintSurface_setUniqueName(surface, CTX_DATA_(BLT_I18NCONTEXT_ID_BRUSH, "Surface"));

  surface->effector_weights = BKE_effector_add_weights(NULL);

  dynamicPaintSurface_updateType(surface);

  BLI_addtail(&canvas->surfaces, surface);

  return surface;
}

bool dynamicPaint_createType(struct DynamicPaintModifierData *pmd, int type, struct Scene *scene)
{
  if (pmd) {
    if (type == MOD_DYNAMICPAINT_TYPE_CANVAS) {
      DynamicPaintCanvasSettings *canvas;
      if (pmd->canvas) {
        dynamicPaint_freeCanvas(pmd);
      }

      canvas = pmd->canvas = MEM_callocN(sizeof(DynamicPaintCanvasSettings),
                                         "DynamicPaint Canvas");
      if (!canvas) {
        return false;
      }
      canvas->pmd = pmd;

      /* Create one surface */
      if (!dynamicPaint_createNewSurface(canvas, scene)) {
        return false;
      }
    }
    else if (type == MOD_DYNAMICPAINT_TYPE_BRUSH) {
      DynamicPaintBrushSettings *brush;
      if (pmd->brush) {
        dynamicPaint_freeBrush(pmd);
      }

      brush = pmd->brush = MEM_callocN(sizeof(DynamicPaintBrushSettings), "DynamicPaint Paint");
      if (!brush) {
        return false;
      }
      brush->pmd = pmd;

      brush->psys = NULL;

      brush->flags = MOD_DPAINT_ABS_ALPHA | MOD_DPAINT_RAMP_ALPHA;
      brush->collision = MOD_DPAINT_COL_VOLUME;

      brush->r = 0.15f;
      brush->g = 0.4f;
      brush->b = 0.8f;
      brush->alpha = 1.0f;
      brush->wetness = 1.0f;

      brush->paint_distance = 1.0f;
      brush->proximity_falloff = MOD_DPAINT_PRFALL_SMOOTH;

      brush->particle_radius = 0.2f;
      brush->particle_smooth = 0.05f;

      brush->wave_type = MOD_DPAINT_WAVEB_CHANGE;
      brush->wave_factor = 1.0f;
      brush->wave_clamp = 0.0f;
      brush->smudge_strength = 0.3f;
      brush->max_velocity = 1.0f;

      /* Paint proximity falloff colorramp. */
      {
        CBData *ramp;

        brush->paint_ramp = BKE_colorband_add(false);
        if (!brush->paint_ramp) {
          return false;
        }
        ramp = brush->paint_ramp->data;
        /* Add default smooth-falloff ramp. */
        ramp[0].r = ramp[0].g = ramp[0].b = ramp[0].a = 1.0f;
        ramp[0].pos = 0.0f;
        ramp[1].r = ramp[1].g = ramp[1].b = ramp[1].pos = 1.0f;
        ramp[1].a = 0.0f;
        pmd->brush->paint_ramp->tot = 2;
      }

      /* Brush velocity ramp. */
      {
        CBData *ramp;

        brush->vel_ramp = BKE_colorband_add(false);
        if (!brush->vel_ramp) {
          return false;
        }
        ramp = brush->vel_ramp->data;
        ramp[0].r = ramp[0].g = ramp[0].b = ramp[0].a = ramp[0].pos = 0.0f;
        ramp[1].r = ramp[1].g = ramp[1].b = ramp[1].a = ramp[1].pos = 1.0f;
        brush->paint_ramp->tot = 2;
      }
    }
  }
  else {
    return false;
  }

  return true;
}

void dynamicPaint_Modifier_copy(const struct DynamicPaintModifierData *pmd,
                                struct DynamicPaintModifierData *tpmd,
                                int flag)
{
  /* Init modifier */
  tpmd->type = pmd->type;
  if (pmd->canvas) {
    dynamicPaint_createType(tpmd, MOD_DYNAMICPAINT_TYPE_CANVAS, NULL);
  }
  if (pmd->brush) {
    dynamicPaint_createType(tpmd, MOD_DYNAMICPAINT_TYPE_BRUSH, NULL);
  }

  /* Copy data */
  if (tpmd->canvas) {
    DynamicPaintSurface *surface;
    tpmd->canvas->pmd = tpmd;
    /* free default surface */
    if (tpmd->canvas->surfaces.first) {
      dynamicPaint_freeSurface(tpmd, tpmd->canvas->surfaces.first);
    }

    tpmd->canvas->active_sur = pmd->canvas->active_sur;

    /* copy existing surfaces */
    for (surface = pmd->canvas->surfaces.first; surface; surface = surface->next) {
      DynamicPaintSurface *t_surface = dynamicPaint_createNewSurface(tpmd->canvas, NULL);
      if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
        /* TODO(sergey): Consider passing some tips to the surface
         * creation to avoid this allocate-and-free cache behavior. */
        BKE_ptcache_free_list(&t_surface->ptcaches);
        tpmd->modifier.flag |= eModifierFlag_SharedCaches;
        t_surface->ptcaches = surface->ptcaches;
        t_surface->pointcache = surface->pointcache;
      }

      /* surface settings */
      t_surface->brush_group = surface->brush_group;
      MEM_freeN(t_surface->effector_weights);
      t_surface->effector_weights = MEM_dupallocN(surface->effector_weights);

      BLI_strncpy(t_surface->name, surface->name, sizeof(t_surface->name));
      t_surface->format = surface->format;
      t_surface->type = surface->type;
      t_surface->disp_type = surface->disp_type;
      t_surface->image_fileformat = surface->image_fileformat;
      t_surface->effect_ui = surface->effect_ui;
      t_surface->init_color_type = surface->init_color_type;
      t_surface->flags = surface->flags;
      t_surface->effect = surface->effect;

      t_surface->image_resolution = surface->image_resolution;
      t_surface->substeps = surface->substeps;
      t_surface->start_frame = surface->start_frame;
      t_surface->end_frame = surface->end_frame;

      copy_v4_v4(t_surface->init_color, surface->init_color);
      t_surface->init_texture = surface->init_texture;
      BLI_strncpy(
          t_surface->init_layername, surface->init_layername, sizeof(t_surface->init_layername));

      t_surface->dry_speed = surface->dry_speed;
      t_surface->diss_speed = surface->diss_speed;
      t_surface->color_dry_threshold = surface->color_dry_threshold;
      t_surface->depth_clamp = surface->depth_clamp;
      t_surface->disp_factor = surface->disp_factor;

      t_surface->spread_speed = surface->spread_speed;
      t_surface->color_spread_speed = surface->color_spread_speed;
      t_surface->shrink_speed = surface->shrink_speed;
      t_surface->drip_vel = surface->drip_vel;
      t_surface->drip_acc = surface->drip_acc;

      t_surface->influence_scale = surface->influence_scale;
      t_surface->radius_scale = surface->radius_scale;

      t_surface->wave_damping = surface->wave_damping;
      t_surface->wave_speed = surface->wave_speed;
      t_surface->wave_timescale = surface->wave_timescale;
      t_surface->wave_spring = surface->wave_spring;
      t_surface->wave_smoothness = surface->wave_smoothness;

      BLI_strncpy(t_surface->uvlayer_name, surface->uvlayer_name, sizeof(t_surface->uvlayer_name));
      BLI_strncpy(t_surface->image_output_path,
                  surface->image_output_path,
                  sizeof(t_surface->image_output_path));
      BLI_strncpy(t_surface->output_name, surface->output_name, sizeof(t_surface->output_name));
      BLI_strncpy(t_surface->output_name2, surface->output_name2, sizeof(t_surface->output_name2));
    }
  }
  if (tpmd->brush) {
    DynamicPaintBrushSettings *brush = pmd->brush, *t_brush = tpmd->brush;
    t_brush->pmd = tpmd;

    t_brush->flags = brush->flags;
    t_brush->collision = brush->collision;

    t_brush->r = brush->r;
    t_brush->g = brush->g;
    t_brush->b = brush->b;
    t_brush->alpha = brush->alpha;
    t_brush->wetness = brush->wetness;

    t_brush->particle_radius = brush->particle_radius;
    t_brush->particle_smooth = brush->particle_smooth;
    t_brush->paint_distance = brush->paint_distance;

    /* NOTE: This is dangerous, as it will generate invalid data in case we are copying between
     * different objects. Extra external code has to be called then to ensure proper remapping of
     * that pointer. See e.g. `BKE_object_copy_particlesystems` or `BKE_object_copy_modifier`. */
    t_brush->psys = brush->psys;

    if (brush->paint_ramp) {
      memcpy(t_brush->paint_ramp, brush->paint_ramp, sizeof(ColorBand));
    }
    if (brush->vel_ramp) {
      memcpy(t_brush->vel_ramp, brush->vel_ramp, sizeof(ColorBand));
    }

    t_brush->proximity_falloff = brush->proximity_falloff;
    t_brush->wave_type = brush->wave_type;
    t_brush->ray_dir = brush->ray_dir;

    t_brush->wave_factor = brush->wave_factor;
    t_brush->wave_clamp = brush->wave_clamp;
    t_brush->max_velocity = brush->max_velocity;
    t_brush->smudge_strength = brush->smudge_strength;
  }
}

/* allocates surface data depending on surface type */
static void dynamicPaint_allocateSurfaceType(DynamicPaintSurface *surface)
{
  PaintSurfaceData *sData = surface->data;

  switch (surface->type) {
    case MOD_DPAINT_SURFACE_T_PAINT:
      sData->type_data = MEM_callocN(sizeof(PaintPoint) * sData->total_points,
                                     "DynamicPaintSurface Data");
      break;
    case MOD_DPAINT_SURFACE_T_DISPLACE:
      sData->type_data = MEM_callocN(sizeof(float) * sData->total_points,
                                     "DynamicPaintSurface DepthData");
      break;
    case MOD_DPAINT_SURFACE_T_WEIGHT:
      sData->type_data = MEM_callocN(sizeof(float) * sData->total_points,
                                     "DynamicPaintSurface WeightData");
      break;
    case MOD_DPAINT_SURFACE_T_WAVE:
      sData->type_data = MEM_callocN(sizeof(PaintWavePoint) * sData->total_points,
                                     "DynamicPaintSurface WaveData");
      break;
  }

  if (sData->type_data == NULL) {
    setError(surface->canvas, N_("Not enough free memory"));
  }
}

static bool surface_usesAdjDistance(DynamicPaintSurface *surface)
{
  return ((surface->type == MOD_DPAINT_SURFACE_T_PAINT && surface->effect) ||
          (surface->type == MOD_DPAINT_SURFACE_T_WAVE));
}

static bool surface_usesAdjData(DynamicPaintSurface *surface)
{
  return (surface_usesAdjDistance(surface) || (surface->format == MOD_DPAINT_SURFACE_F_VERTEX &&
                                               surface->flags & MOD_DPAINT_ANTIALIAS));
}

/* initialize surface adjacency data */
static void dynamicPaint_initAdjacencyData(DynamicPaintSurface *surface, const bool force_init)
{
  PaintSurfaceData *sData = surface->data;
  Mesh *mesh = dynamicPaint_canvas_mesh_get(surface->canvas);
  PaintAdjData *ad;
  int *temp_data;
  int neigh_points = 0;

  if (!force_init && !surface_usesAdjData(surface)) {
    return;
  }

  if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
    /* For vertex format, neighbors are connected by edges */
    neigh_points = 2 * mesh->totedge;
  }
  else if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
    neigh_points = sData->total_points * 8;
  }

  if (!neigh_points) {
    return;
  }

  /* allocate memory */
  ad = sData->adj_data = MEM_callocN(sizeof(PaintAdjData), "Surface Adj Data");
  if (!ad) {
    return;
  }
  ad->n_index = MEM_callocN(sizeof(int) * sData->total_points, "Surface Adj Index");
  ad->n_num = MEM_callocN(sizeof(int) * sData->total_points, "Surface Adj Counts");
  temp_data = MEM_callocN(sizeof(int) * sData->total_points, "Temp Adj Data");
  ad->n_target = MEM_callocN(sizeof(int) * neigh_points, "Surface Adj Targets");
  ad->flags = MEM_callocN(sizeof(int) * sData->total_points, "Surface Adj Flags");
  ad->total_targets = neigh_points;
  ad->border = NULL;
  ad->total_border = 0;

  /* in case of allocation error, free memory */
  if (!ad->n_index || !ad->n_num || !ad->n_target || !temp_data) {
    dynamicPaint_freeAdjData(sData);
    if (temp_data) {
      MEM_freeN(temp_data);
    }
    setError(surface->canvas, N_("Not enough free memory"));
    return;
  }

  if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
    /* For vertex format, count every vertex that is connected by an edge */
    int numOfEdges = mesh->totedge;
    int numOfPolys = mesh->totpoly;
    struct MEdge *edge = mesh->medge;
    struct MPoly *mpoly = mesh->mpoly;
    struct MLoop *mloop = mesh->mloop;

    /* count number of edges per vertex */
    for (int i = 0; i < numOfEdges; i++) {
      ad->n_num[edge[i].v1]++;
      ad->n_num[edge[i].v2]++;

      temp_data[edge[i].v1]++;
      temp_data[edge[i].v2]++;
    }

    /* also add number of vertices to temp_data
     * to locate points on "mesh edge" */
    for (int i = 0; i < numOfPolys; i++) {
      for (int j = 0; j < mpoly[i].totloop; j++) {
        temp_data[mloop[mpoly[i].loopstart + j].v]++;
      }
    }

    /* now check if total number of edges+faces for
     * each vertex is even, if not -> vertex is on mesh edge */
    for (int i = 0; i < sData->total_points; i++) {
      if ((temp_data[i] % 2) || (temp_data[i] < 4)) {
        ad->flags[i] |= ADJ_ON_MESH_EDGE;
      }

      /* reset temp data */
      temp_data[i] = 0;
    }

    /* order n_index array */
    int n_pos = 0;
    for (int i = 0; i < sData->total_points; i++) {
      ad->n_index[i] = n_pos;
      n_pos += ad->n_num[i];
    }

    /* and now add neighbor data using that info */
    for (int i = 0; i < numOfEdges; i++) {
      /* first vertex */
      int index = edge[i].v1;
      n_pos = ad->n_index[index] + temp_data[index];
      ad->n_target[n_pos] = edge[i].v2;
      temp_data[index]++;

      /* second vertex */
      index = edge[i].v2;
      n_pos = ad->n_index[index] + temp_data[index];
      ad->n_target[n_pos] = edge[i].v1;
      temp_data[index]++;
    }
  }
  else if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
    /* for image sequences, only allocate memory.
     * bake initialization takes care of rest */
  }

  MEM_freeN(temp_data);
}

typedef struct DynamicPaintSetInitColorData {
  const DynamicPaintSurface *surface;

  const MLoop *mloop;
  const MLoopUV *mloopuv;
  const MLoopTri *mlooptri;
  const MLoopCol *mloopcol;
  struct ImagePool *pool;

  const bool scene_color_manage;
} DynamicPaintSetInitColorData;

static void dynamic_paint_set_init_color_tex_to_vcol_cb(
    void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintSetInitColorData *data = userdata;

  const PaintSurfaceData *sData = data->surface->data;
  PaintPoint *pPoint = (PaintPoint *)sData->type_data;

  const MLoop *mloop = data->mloop;
  const MLoopTri *mlooptri = data->mlooptri;
  const MLoopUV *mloopuv = data->mloopuv;
  struct ImagePool *pool = data->pool;
  Tex *tex = data->surface->init_texture;

  const bool scene_color_manage = data->scene_color_manage;

  float uv[3] = {0.0f};

  for (int j = 3; j--;) {
    TexResult texres = {0};
    const unsigned int vert = mloop[mlooptri[i].tri[j]].v;

    /* remap to [-1.0, 1.0] */
    uv[0] = mloopuv[mlooptri[i].tri[j]].uv[0] * 2.0f - 1.0f;
    uv[1] = mloopuv[mlooptri[i].tri[j]].uv[1] * 2.0f - 1.0f;

    multitex_ext_safe(tex, uv, &texres, pool, scene_color_manage, false);

    if (texres.tin > pPoint[vert].color[3]) {
      copy_v3_v3(pPoint[vert].color, texres.trgba);
      pPoint[vert].color[3] = texres.tin;
    }
  }
}

static void dynamic_paint_set_init_color_tex_to_imseq_cb(
    void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintSetInitColorData *data = userdata;

  const PaintSurfaceData *sData = data->surface->data;
  PaintPoint *pPoint = (PaintPoint *)sData->type_data;

  const MLoopTri *mlooptri = data->mlooptri;
  const MLoopUV *mloopuv = data->mloopuv;
  Tex *tex = data->surface->init_texture;
  ImgSeqFormatData *f_data = (ImgSeqFormatData *)sData->format_data;
  const int samples = (data->surface->flags & MOD_DPAINT_ANTIALIAS) ? 5 : 1;

  const bool scene_color_manage = data->scene_color_manage;

  float uv[9] = {0.0f};
  float uv_final[3] = {0.0f};

  TexResult texres = {0};

  /* collect all uvs */
  for (int j = 3; j--;) {
    copy_v2_v2(&uv[j * 3], mloopuv[mlooptri[f_data->uv_p[i].tri_index].tri[j]].uv);
  }

  /* interpolate final uv pos */
  interp_v3_v3v3v3(uv_final, &uv[0], &uv[3], &uv[6], f_data->barycentricWeights[i * samples].v);
  /* remap to [-1.0, 1.0] */
  uv_final[0] = uv_final[0] * 2.0f - 1.0f;
  uv_final[1] = uv_final[1] * 2.0f - 1.0f;

  multitex_ext_safe(tex, uv_final, &texres, NULL, scene_color_manage, false);

  /* apply color */
  copy_v3_v3(pPoint[i].color, texres.trgba);
  pPoint[i].color[3] = texres.tin;
}

static void dynamic_paint_set_init_color_vcol_to_imseq_cb(
    void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintSetInitColorData *data = userdata;

  const PaintSurfaceData *sData = data->surface->data;
  PaintPoint *pPoint = (PaintPoint *)sData->type_data;

  const MLoopTri *mlooptri = data->mlooptri;
  const MLoopCol *mloopcol = data->mloopcol;
  ImgSeqFormatData *f_data = (ImgSeqFormatData *)sData->format_data;
  const int samples = (data->surface->flags & MOD_DPAINT_ANTIALIAS) ? 5 : 1;

  const int tri_idx = f_data->uv_p[i].tri_index;
  float colors[3][4];
  float final_color[4];

  /* collect color values */
  for (int j = 3; j--;) {
    rgba_uchar_to_float(colors[j], (const unsigned char *)&mloopcol[mlooptri[tri_idx].tri[j]].r);
  }

  /* interpolate final color */
  interp_v4_v4v4v4(final_color, UNPACK3(colors), f_data->barycentricWeights[i * samples].v);

  copy_v4_v4(pPoint[i].color, final_color);
}

static void dynamicPaint_setInitialColor(const Scene *scene, DynamicPaintSurface *surface)
{
  PaintSurfaceData *sData = surface->data;
  PaintPoint *pPoint = (PaintPoint *)sData->type_data;
  Mesh *mesh = dynamicPaint_canvas_mesh_get(surface->canvas);
  const bool scene_color_manage = BKE_scene_check_color_management_enabled(scene);

  if (surface->type != MOD_DPAINT_SURFACE_T_PAINT) {
    return;
  }

  if (surface->init_color_type == MOD_DPAINT_INITIAL_NONE) {
    return;
  }

  /* Single color */
  if (surface->init_color_type == MOD_DPAINT_INITIAL_COLOR) {
    /* apply color to every surface point */
    for (int i = 0; i < sData->total_points; i++) {
      copy_v4_v4(pPoint[i].color, surface->init_color);
    }
  }
  /* UV mapped texture */
  else if (surface->init_color_type == MOD_DPAINT_INITIAL_TEXTURE) {
    Tex *tex = surface->init_texture;

    const MLoop *mloop = mesh->mloop;
    const MLoopTri *mlooptri = BKE_mesh_runtime_looptri_ensure(mesh);
    const int tottri = BKE_mesh_runtime_looptri_len(mesh);
    const MLoopUV *mloopuv = NULL;

    char uvname[MAX_CUSTOMDATA_LAYER_NAME];

    if (!tex) {
      return;
    }

    /* get uv map */
    CustomData_validate_layer_name(&mesh->ldata, CD_MLOOPUV, surface->init_layername, uvname);
    mloopuv = CustomData_get_layer_named(&mesh->ldata, CD_MLOOPUV, uvname);
    if (!mloopuv) {
      return;
    }

    /* for vertex surface loop through tfaces and find uv color
     * that provides highest alpha */
    if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
      struct ImagePool *pool = BKE_image_pool_new();

      DynamicPaintSetInitColorData data = {
          .surface = surface,
          .mloop = mloop,
          .mlooptri = mlooptri,
          .mloopuv = mloopuv,
          .pool = pool,
          .scene_color_manage = scene_color_manage,
      };
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (tottri > 1000);
      BLI_task_parallel_range(
          0, tottri, &data, dynamic_paint_set_init_color_tex_to_vcol_cb, &settings);
      BKE_image_pool_free(pool);
    }
    else if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
      DynamicPaintSetInitColorData data = {
          .surface = surface,
          .mlooptri = mlooptri,
          .mloopuv = mloopuv,
          .scene_color_manage = scene_color_manage,
      };
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (sData->total_points > 1000);
      BLI_task_parallel_range(
          0, sData->total_points, &data, dynamic_paint_set_init_color_tex_to_imseq_cb, &settings);
    }
  }
  /* vertex color layer */
  else if (surface->init_color_type == MOD_DPAINT_INITIAL_VERTEXCOLOR) {

    /* for vertex surface, just copy colors from mcol */
    if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
      const MLoop *mloop = mesh->mloop;
      const int totloop = mesh->totloop;
      const MLoopCol *col = CustomData_get_layer_named(
          &mesh->ldata, CD_MLOOPCOL, surface->init_layername);
      if (!col) {
        return;
      }

      for (int i = 0; i < totloop; i++) {
        rgba_uchar_to_float(pPoint[mloop[i].v].color, (const unsigned char *)&col[i].r);
      }
    }
    else if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
      const MLoopTri *mlooptri = BKE_mesh_runtime_looptri_ensure(mesh);
      MLoopCol *col = CustomData_get_layer_named(
          &mesh->ldata, CD_MLOOPCOL, surface->init_layername);
      if (!col) {
        return;
      }

      DynamicPaintSetInitColorData data = {
          .surface = surface,
          .mlooptri = mlooptri,
          .mloopcol = col,
      };
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (sData->total_points > 1000);
      BLI_task_parallel_range(
          0, sData->total_points, &data, dynamic_paint_set_init_color_vcol_to_imseq_cb, &settings);
    }
  }
}

void dynamicPaint_clearSurface(const Scene *scene, DynamicPaintSurface *surface)
{
  PaintSurfaceData *sData = surface->data;
  if (sData && sData->type_data) {
    unsigned int data_size;

    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      data_size = sizeof(PaintPoint);
    }
    else if (surface->type == MOD_DPAINT_SURFACE_T_WAVE) {
      data_size = sizeof(PaintWavePoint);
    }
    else {
      data_size = sizeof(float);
    }

    memset(sData->type_data, 0, data_size * sData->total_points);

    /* set initial color */
    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      dynamicPaint_setInitialColor(scene, surface);
    }

    if (sData->bData) {
      sData->bData->clear = 1;
    }
  }
}

bool dynamicPaint_resetSurface(const Scene *scene, DynamicPaintSurface *surface)
{
  int numOfPoints = dynamicPaint_surfaceNumOfPoints(surface);
  /* free existing data */
  if (surface->data) {
    dynamicPaint_freeSurfaceData(surface);
  }

  /* don't reallocate for image sequence types. they get handled only on bake */
  if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
    return true;
  }
  if (numOfPoints < 1) {
    return false;
  }

  /* allocate memory */
  surface->data = MEM_callocN(sizeof(PaintSurfaceData), "PaintSurfaceData");
  if (!surface->data) {
    return false;
  }

  /* allocate data depending on surface type and format */
  surface->data->total_points = numOfPoints;
  dynamicPaint_allocateSurfaceType(surface);
  dynamicPaint_initAdjacencyData(surface, false);

  /* set initial color */
  if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
    dynamicPaint_setInitialColor(scene, surface);
  }

  return true;
}

/* make sure allocated surface size matches current requirements */
static bool dynamicPaint_checkSurfaceData(const Scene *scene, DynamicPaintSurface *surface)
{
  if (!surface->data ||
      ((dynamicPaint_surfaceNumOfPoints(surface) != surface->data->total_points))) {
    return dynamicPaint_resetSurface(scene, surface);
  }
  return true;
}

/***************************** Modifier processing ******************************/

typedef struct DynamicPaintModifierApplyData {
  const DynamicPaintSurface *surface;
  Object *ob;

  MVert *mvert;
  const float (*vert_normals)[3];
  const MLoop *mloop;
  const MPoly *mpoly;

  float (*fcolor)[4];
  MLoopCol *mloopcol;
  MLoopCol *mloopcol_wet;
} DynamicPaintModifierApplyData;

static void dynamic_paint_apply_surface_displace_cb(void *__restrict userdata,
                                                    const int i,
                                                    const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintModifierApplyData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  MVert *mvert = data->mvert;

  const float *value = (float *)surface->data->type_data;
  const float val = value[i] * surface->disp_factor;

  /* same as 'mvert[i].co[0] -= normal[0] * val' etc. */
  madd_v3_v3fl(mvert[i].co, data->vert_normals[i], -val);
}

/* apply displacing vertex surface to the derived mesh */
static void dynamicPaint_applySurfaceDisplace(DynamicPaintSurface *surface, Mesh *result)
{
  PaintSurfaceData *sData = surface->data;

  if (!sData || surface->format != MOD_DPAINT_SURFACE_F_VERTEX) {
    return;
  }

  /* displace paint */
  if (surface->type == MOD_DPAINT_SURFACE_T_DISPLACE) {
    MVert *mvert = result->mvert;

    DynamicPaintModifierApplyData data = {
        .surface = surface,
        .mvert = mvert,
        .vert_normals = BKE_mesh_vertex_normals_ensure(result),
    };
    TaskParallelSettings settings;
    BLI_parallel_range_settings_defaults(&settings);
    settings.use_threading = (sData->total_points > 10000);
    BLI_task_parallel_range(
        0, sData->total_points, &data, dynamic_paint_apply_surface_displace_cb, &settings);
  }
}

static void dynamic_paint_apply_surface_vpaint_blend_cb(
    void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintModifierApplyData *data = userdata;

  PaintPoint *pPoint = (PaintPoint *)data->surface->data->type_data;
  float(*fcolor)[4] = data->fcolor;

  /* blend dry and wet layer */
  blendColors(
      pPoint[i].color, pPoint[i].color[3], pPoint[i].e_color, pPoint[i].e_color[3], fcolor[i]);
}

static void dynamic_paint_apply_surface_vpaint_cb(void *__restrict userdata,
                                                  const int p_index,
                                                  const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintModifierApplyData *data = userdata;

  const MLoop *mloop = data->mloop;
  const MPoly *mpoly = data->mpoly;

  const DynamicPaintSurface *surface = data->surface;
  PaintPoint *pPoint = (PaintPoint *)surface->data->type_data;
  float(*fcolor)[4] = data->fcolor;

  MLoopCol *mloopcol = data->mloopcol;
  MLoopCol *mloopcol_wet = data->mloopcol_wet;

  for (int j = 0; j < mpoly[p_index].totloop; j++) {
    const int l_index = mpoly[p_index].loopstart + j;
    const int v_index = mloop[l_index].v;

    /* save layer data to output layer */
    /* apply color */
    if (mloopcol) {
      rgba_float_to_uchar((unsigned char *)&mloopcol[l_index].r, fcolor[v_index]);
    }
    /* apply wetness */
    if (mloopcol_wet) {
      const char c = unit_float_to_uchar_clamp(pPoint[v_index].wetness);
      mloopcol_wet[l_index].r = c;
      mloopcol_wet[l_index].g = c;
      mloopcol_wet[l_index].b = c;
      mloopcol_wet[l_index].a = 255;
    }
  }
}

static void dynamic_paint_apply_surface_wave_cb(void *__restrict userdata,
                                                const int i,
                                                const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintModifierApplyData *data = userdata;

  PaintWavePoint *wPoint = (PaintWavePoint *)data->surface->data->type_data;
  MVert *mvert = data->mvert;

  madd_v3_v3fl(mvert[i].co, data->vert_normals[i], wPoint[i].height);
}

/*
 * Apply canvas data to the object derived mesh
 */
static Mesh *dynamicPaint_Modifier_apply(DynamicPaintModifierData *pmd, Object *ob, Mesh *mesh)
{
  Mesh *result = BKE_mesh_copy_for_eval(mesh, false);

  if (pmd->canvas && !(pmd->canvas->flags & MOD_DPAINT_BAKING) &&
      pmd->type == MOD_DYNAMICPAINT_TYPE_CANVAS) {

    DynamicPaintSurface *surface;
    bool update_normals = false;

    /* loop through surfaces */
    for (surface = pmd->canvas->surfaces.first; surface; surface = surface->next) {
      PaintSurfaceData *sData = surface->data;

      if (surface->format != MOD_DPAINT_SURFACE_F_IMAGESEQ && sData) {
        if (!(surface->flags & MOD_DPAINT_ACTIVE)) {
          continue;
        }

        /* process vertex surface previews */
        if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {

          /* vertex color paint */
          if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
            MLoop *mloop = result->mloop;
            const int totloop = result->totloop;
            MPoly *mpoly = result->mpoly;
            const int totpoly = result->totpoly;

            /* paint is stored on dry and wet layers, so mix final color first */
            float(*fcolor)[4] = MEM_callocN(sizeof(*fcolor) * sData->total_points,
                                            "Temp paint color");

            DynamicPaintModifierApplyData data = {
                .surface = surface,
                .fcolor = fcolor,
            };
            {
              TaskParallelSettings settings;
              BLI_parallel_range_settings_defaults(&settings);
              settings.use_threading = (sData->total_points > 1000);
              BLI_task_parallel_range(0,
                                      sData->total_points,
                                      &data,
                                      dynamic_paint_apply_surface_vpaint_blend_cb,
                                      &settings);
            }

            /* paint layer */
            MLoopCol *mloopcol = CustomData_get_layer_named(
                &result->ldata, CD_MLOOPCOL, surface->output_name);
            /* if output layer is lost from a constructive modifier, re-add it */
            if (!mloopcol && dynamicPaint_outputLayerExists(surface, ob, 0)) {
              mloopcol = CustomData_add_layer_named(
                  &result->ldata, CD_MLOOPCOL, CD_CALLOC, NULL, totloop, surface->output_name);
            }

            /* wet layer */
            MLoopCol *mloopcol_wet = CustomData_get_layer_named(
                &result->ldata, CD_MLOOPCOL, surface->output_name2);
            /* if output layer is lost from a constructive modifier, re-add it */
            if (!mloopcol_wet && dynamicPaint_outputLayerExists(surface, ob, 1)) {
              mloopcol_wet = CustomData_add_layer_named(
                  &result->ldata, CD_MLOOPCOL, CD_CALLOC, NULL, totloop, surface->output_name2);
            }

            data.ob = ob;
            data.mloop = mloop;
            data.mpoly = mpoly;
            data.mloopcol = mloopcol;
            data.mloopcol_wet = mloopcol_wet;

            {
              TaskParallelSettings settings;
              BLI_parallel_range_settings_defaults(&settings);
              settings.use_threading = (totpoly > 1000);
              BLI_task_parallel_range(
                  0, totpoly, &data, dynamic_paint_apply_surface_vpaint_cb, &settings);
            }

            MEM_freeN(fcolor);

            /* Mark tessellated CD layers as dirty. */
            // result->dirty |= DM_DIRTY_TESS_CDLAYERS;
          }
          /* vertex group paint */
          else if (surface->type == MOD_DPAINT_SURFACE_T_WEIGHT) {
            int defgrp_index = BKE_object_defgroup_name_index(ob, surface->output_name);
            MDeformVert *dvert = CustomData_get_layer(&result->vdata, CD_MDEFORMVERT);
            float *weight = (float *)sData->type_data;

            /* apply weights into a vertex group, if doesn't exists add a new layer */
            if (defgrp_index != -1 && !dvert && (surface->output_name[0] != '\0')) {
              dvert = CustomData_add_layer(
                  &result->vdata, CD_MDEFORMVERT, CD_CALLOC, NULL, sData->total_points);
              /* Make the dvert layer easily accessible from the mesh data. */
              result->dvert = dvert;
            }
            if (defgrp_index != -1 && dvert) {
              for (int i = 0; i < sData->total_points; i++) {
                MDeformVert *dv = &dvert[i];
                MDeformWeight *def_weight = BKE_defvert_find_index(dv, defgrp_index);

                /* skip if weight value is 0 and no existing weight is found */
                if ((def_weight != NULL) || (weight[i] != 0.0f)) {
                  /* if not found, add a weight for it */
                  if (def_weight == NULL) {
                    def_weight = BKE_defvert_ensure_index(dv, defgrp_index);
                  }

                  /* set weight value */
                  def_weight->weight = weight[i];
                }
              }
            }
          }
          /* wave simulation */
          else if (surface->type == MOD_DPAINT_SURFACE_T_WAVE) {
            MVert *mvert = result->mvert;

            DynamicPaintModifierApplyData data = {
                .surface = surface,
                .mvert = mvert,
                .vert_normals = BKE_mesh_vertex_normals_ensure(result),
            };
            TaskParallelSettings settings;
            BLI_parallel_range_settings_defaults(&settings);
            settings.use_threading = (sData->total_points > 1000);
            BLI_task_parallel_range(
                0, sData->total_points, &data, dynamic_paint_apply_surface_wave_cb, &settings);
            update_normals = true;
          }

          /* displace */
          if (surface->type == MOD_DPAINT_SURFACE_T_DISPLACE) {
            dynamicPaint_applySurfaceDisplace(surface, result);
            update_normals = true;
          }
        }
      }
    }

    if (update_normals) {
      BKE_mesh_normals_tag_dirty(result);
    }
  }
  /* make a copy of mesh to use as brush data */
  else if (pmd->brush && pmd->type == MOD_DYNAMICPAINT_TYPE_BRUSH) {
    DynamicPaintRuntime *runtime_data = dynamicPaint_Modifier_runtime_ensure(pmd);
    if (runtime_data->brush_mesh != NULL) {
      BKE_id_free(NULL, runtime_data->brush_mesh);
    }
    runtime_data->brush_mesh = BKE_mesh_copy_for_eval(result, false);
  }

  return result;
}

void dynamicPaint_cacheUpdateFrames(DynamicPaintSurface *surface)
{
  if (surface->pointcache) {
    surface->pointcache->startframe = surface->start_frame;
    surface->pointcache->endframe = surface->end_frame;
  }
}

static void canvas_copyMesh(DynamicPaintCanvasSettings *canvas, Mesh *mesh)
{
  DynamicPaintRuntime *runtime = dynamicPaint_Modifier_runtime_ensure(canvas->pmd);
  if (runtime->canvas_mesh != NULL) {
    BKE_id_free(NULL, runtime->canvas_mesh);
  }

  runtime->canvas_mesh = BKE_mesh_copy_for_eval(mesh, false);
}

/*
 * Updates derived mesh copy and processes dynamic paint step / caches.
 */
static void dynamicPaint_frameUpdate(DynamicPaintModifierData *pmd,
                                     struct Depsgraph *depsgraph,
                                     Scene *scene,
                                     Object *ob,
                                     Mesh *mesh)
{
  if (pmd->canvas) {
    DynamicPaintCanvasSettings *canvas = pmd->canvas;
    DynamicPaintSurface *surface = canvas->surfaces.first;

    /* update derived mesh copy */
    canvas_copyMesh(canvas, mesh);

    /* in case image sequence baking, stop here */
    if (canvas->flags & MOD_DPAINT_BAKING) {
      return;
    }

    /* loop through surfaces */
    for (; surface; surface = surface->next) {
      int current_frame = (int)scene->r.cfra;
      bool no_surface_data;

      /* free bake data if not required anymore */
      surface_freeUnusedData(surface);

      /* image sequences are handled by bake operator */
      if ((surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) ||
          !(surface->flags & MOD_DPAINT_ACTIVE)) {
        continue;
      }

      /* make sure surface is valid */
      no_surface_data = surface->data == NULL;
      if (!dynamicPaint_checkSurfaceData(scene, surface)) {
        continue;
      }

      /* limit frame range */
      CLAMP(current_frame, surface->start_frame, surface->end_frame);

      if (no_surface_data || current_frame != surface->current_frame ||
          (int)scene->r.cfra == surface->start_frame) {
        PointCache *cache = surface->pointcache;
        PTCacheID pid;
        surface->current_frame = current_frame;

        /* read point cache */
        BKE_ptcache_id_from_dynamicpaint(&pid, ob, surface);
        pid.cache->startframe = surface->start_frame;
        pid.cache->endframe = surface->end_frame;
        BKE_ptcache_id_time(&pid, scene, (float)scene->r.cfra, NULL, NULL, NULL);

        /* reset non-baked cache at first frame */
        if ((int)scene->r.cfra == surface->start_frame && !(cache->flag & PTCACHE_BAKED)) {
          cache->flag |= PTCACHE_REDO_NEEDED;
          BKE_ptcache_id_reset(scene, &pid, PTCACHE_RESET_OUTDATED);
          cache->flag &= ~PTCACHE_REDO_NEEDED;
        }

        /* try to read from cache */
        bool can_simulate = ((int)scene->r.cfra == current_frame) &&
                            !(cache->flag & PTCACHE_BAKED);

        if (BKE_ptcache_read(&pid, (float)scene->r.cfra, can_simulate)) {
          BKE_ptcache_validate(cache, (int)scene->r.cfra);
        }
        /* if read failed and we're on surface range do recalculate */
        else if (can_simulate) {
          /* calculate surface frame */
          canvas->flags |= MOD_DPAINT_BAKING;
          dynamicPaint_calculateFrame(surface, depsgraph, scene, ob, current_frame);
          canvas->flags &= ~MOD_DPAINT_BAKING;

          /* restore canvas mesh if required */
          if (surface->type == MOD_DPAINT_SURFACE_T_DISPLACE &&
              surface->flags & MOD_DPAINT_DISP_INCREMENTAL && surface->next) {
            canvas_copyMesh(canvas, mesh);
          }

          BKE_ptcache_validate(cache, surface->current_frame);
          BKE_ptcache_write(&pid, surface->current_frame);
        }
      }
    }
  }
}

Mesh *dynamicPaint_Modifier_do(DynamicPaintModifierData *pmd,
                               struct Depsgraph *depsgraph,
                               Scene *scene,
                               Object *ob,
                               Mesh *mesh)
{
  /* Update canvas data for a new frame */
  dynamicPaint_frameUpdate(pmd, depsgraph, scene, ob, mesh);

  /* Return output mesh */
  return dynamicPaint_Modifier_apply(pmd, ob, mesh);
}

/* -------------------------------------------------------------------- */
/** \name Image Sequence / UV Image Surface Calls
 * \{ */

/* Create a surface for uv image sequence format. */
#define JITTER_SAMPLES \
  { \
    0.0f, 0.0f, -0.2f, -0.4f, 0.2f, 0.4f, 0.4f, -0.2f, -0.4f, 0.3f, \
  }

typedef struct DynamicPaintCreateUVSurfaceData {
  const DynamicPaintSurface *surface;

  PaintUVPoint *tempPoints;
  Vec3f *tempWeights;

  const MLoopTri *mlooptri;
  const MLoopUV *mloopuv;
  const MLoop *mloop;
  const int tottri;

  const Bounds2D *faceBB;
  uint32_t *active_points;
} DynamicPaintCreateUVSurfaceData;

static void dynamic_paint_create_uv_surface_direct_cb(
    void *__restrict userdata, const int ty, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintCreateUVSurfaceData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  PaintUVPoint *tempPoints = data->tempPoints;
  Vec3f *tempWeights = data->tempWeights;

  const MLoopTri *mlooptri = data->mlooptri;
  const MLoopUV *mloopuv = data->mloopuv;
  const MLoop *mloop = data->mloop;
  const int tottri = data->tottri;

  const Bounds2D *faceBB = data->faceBB;

  const float jitter5sample[10] = JITTER_SAMPLES;
  const int aa_samples = (surface->flags & MOD_DPAINT_ANTIALIAS) ? 5 : 1;
  const int w = surface->image_resolution;
  const int h = w;

  for (int tx = 0; tx < w; tx++) {
    const int index = tx + w * ty;
    PaintUVPoint *tPoint = &tempPoints[index];
    float point[5][2];

    /* Init per pixel settings */
    tPoint->tri_index = -1;
    tPoint->neighbor_pixel = -1;
    tPoint->pixel_index = index;

    /* Actual pixel center, used when collision is found */
    point[0][0] = ((float)tx + 0.5f) / w;
    point[0][1] = ((float)ty + 0.5f) / h;

    /*
     * A pixel middle sample isn't enough to find very narrow polygons
     * So using 4 samples of each corner too
     */
    point[1][0] = ((float)tx) / w;
    point[1][1] = ((float)ty) / h;

    point[2][0] = ((float)tx + 1) / w;
    point[2][1] = ((float)ty) / h;

    point[3][0] = ((float)tx) / w;
    point[3][1] = ((float)ty + 1) / h;

    point[4][0] = ((float)tx + 1) / w;
    point[4][1] = ((float)ty + 1) / h;

    /* Loop through samples, starting from middle point */
    for (int sample = 0; sample < 5; sample++) {
      /* Loop through every face in the mesh */
      /* XXX TODO: This is *horrible* with big meshes, should use a 2D BVHTree over UV tris here!
       */
      for (int i = 0; i < tottri; i++) {
        /* Check uv bb */
        if ((faceBB[i].min[0] > point[sample][0]) || (faceBB[i].min[1] > point[sample][1]) ||
            (faceBB[i].max[0] < point[sample][0]) || (faceBB[i].max[1] < point[sample][1])) {
          continue;
        }

        const float *uv1 = mloopuv[mlooptri[i].tri[0]].uv;
        const float *uv2 = mloopuv[mlooptri[i].tri[1]].uv;
        const float *uv3 = mloopuv[mlooptri[i].tri[2]].uv;

        /* If point is inside the face */
        if (isect_point_tri_v2(point[sample], uv1, uv2, uv3) != 0) {
          float uv[2];

          /* Add b-weights per anti-aliasing sample */
          for (int j = 0; j < aa_samples; j++) {
            uv[0] = point[0][0] + jitter5sample[j * 2] / w;
            uv[1] = point[0][1] + jitter5sample[j * 2 + 1] / h;

            barycentric_weights_v2(uv1, uv2, uv3, uv, tempWeights[index * aa_samples + j].v);
          }

          /* Set surface point face values */
          tPoint->tri_index = i;

          /* save vertex indexes */
          tPoint->v1 = mloop[mlooptri[i].tri[0]].v;
          tPoint->v2 = mloop[mlooptri[i].tri[1]].v;
          tPoint->v3 = mloop[mlooptri[i].tri[2]].v;

          sample = 5; /* make sure we exit sample loop as well */
          break;
        }
      }
    }
  }
}

static void dynamic_paint_create_uv_surface_neighbor_cb(
    void *__restrict userdata, const int ty, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintCreateUVSurfaceData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  PaintUVPoint *tempPoints = data->tempPoints;
  Vec3f *tempWeights = data->tempWeights;

  const MLoopTri *mlooptri = data->mlooptri;
  const MLoopUV *mloopuv = data->mloopuv;
  const MLoop *mloop = data->mloop;

  uint32_t *active_points = data->active_points;

  const float jitter5sample[10] = JITTER_SAMPLES;
  const int aa_samples = (surface->flags & MOD_DPAINT_ANTIALIAS) ? 5 : 1;
  const int w = surface->image_resolution;
  const int h = w;

  for (int tx = 0; tx < w; tx++) {
    const int index = tx + w * ty;
    PaintUVPoint *tPoint = &tempPoints[index];

    /* If point isn't on canvas mesh */
    if (tPoint->tri_index == -1) {
      float point[2];

      /* get loop area */
      const int u_min = (tx > 0) ? -1 : 0;
      const int u_max = (tx < (w - 1)) ? 1 : 0;
      const int v_min = (ty > 0) ? -1 : 0;
      const int v_max = (ty < (h - 1)) ? 1 : 0;

      point[0] = ((float)tx + 0.5f) / w;
      point[1] = ((float)ty + 0.5f) / h;

      /* search through defined area for neighbor, checking grid directions first */
      for (int ni = 0; ni < 8; ni++) {
        int u = neighStraightX[ni];
        int v = neighStraightY[ni];

        if (u >= u_min && u <= u_max && v >= v_min && v <= v_max) {
          /* if not this pixel itself */
          if (u != 0 || v != 0) {
            const int ind = (tx + u) + w * (ty + v);

            /* if neighbor has index */
            if (tempPoints[ind].neighbor_pixel == -1 && tempPoints[ind].tri_index != -1) {
              float uv[2];
              const int i = tempPoints[ind].tri_index;
              const float *uv1 = mloopuv[mlooptri[i].tri[0]].uv;
              const float *uv2 = mloopuv[mlooptri[i].tri[1]].uv;
              const float *uv3 = mloopuv[mlooptri[i].tri[2]].uv;

              /* tri index */
              /* There is a low possibility of actually having a neighbor point which tri is
               * already set from another neighbor in a separate thread here.
               * Checking for both tri_index and neighbor_pixel above reduces that probability
               * but it remains possible.
               * That atomic op (and its memory fence) ensures tPoint->neighbor_pixel is set
               * to non--1 *before* its tri_index is set (i.e. that it cannot be used a neighbor).
               */
              tPoint->neighbor_pixel = ind - 1;
              atomic_add_and_fetch_uint32(&tPoint->neighbor_pixel, 1);
              tPoint->tri_index = i;

              /* Now calculate pixel data for this pixel as it was on polygon surface */
              /* Add b-weights per anti-aliasing sample */
              for (int j = 0; j < aa_samples; j++) {
                uv[0] = point[0] + jitter5sample[j * 2] / w;
                uv[1] = point[1] + jitter5sample[j * 2 + 1] / h;
                barycentric_weights_v2(uv1, uv2, uv3, uv, tempWeights[index * aa_samples + j].v);
              }

              /* save vertex indexes */
              tPoint->v1 = mloop[mlooptri[i].tri[0]].v;
              tPoint->v2 = mloop[mlooptri[i].tri[1]].v;
              tPoint->v3 = mloop[mlooptri[i].tri[2]].v;

              break;
            }
          }
        }
      }
    }

    /* Increase the final number of active surface points if relevant. */
    if (tPoint->tri_index != -1) {
      atomic_add_and_fetch_uint32(active_points, 1);
    }
  }
}

#undef JITTER_SAMPLES

static float dist_squared_to_looptri_uv_edges(const MLoopTri *mlooptri,
                                              const MLoopUV *mloopuv,
                                              int tri_index,
                                              const float point[2])
{
  BLI_assert(tri_index >= 0);

  float min_distance = FLT_MAX;

  for (int i = 0; i < 3; i++) {
    const float dist_squared = dist_squared_to_line_segment_v2(
        point,
        mloopuv[mlooptri[tri_index].tri[(i + 0)]].uv,
        mloopuv[mlooptri[tri_index].tri[(i + 1) % 3]].uv);

    if (dist_squared < min_distance) {
      min_distance = dist_squared;
    }
  }

  return min_distance;
}

typedef struct DynamicPaintFindIslandBorderData {
  const MeshElemMap *vert_to_looptri_map;
  int w, h, px, py;

  int best_index;
  float best_weight;
} DynamicPaintFindIslandBorderData;

static void dynamic_paint_find_island_border(const DynamicPaintCreateUVSurfaceData *data,
                                             DynamicPaintFindIslandBorderData *bdata,
                                             int tri_index,
                                             const float pixel[2],
                                             int in_edge,
                                             int depth);

/* Tries to find the neighboring pixel in given (uv space) direction.
 * Result is used by effect system to move paint on the surface.
 *
 * px, py : origin pixel x and y
 * n_index : lookup direction index (use neighX, neighY to get final index)
 */
static int dynamic_paint_find_neighbor_pixel(const DynamicPaintCreateUVSurfaceData *data,
                                             const MeshElemMap *vert_to_looptri_map,
                                             const int w,
                                             const int h,
                                             const int px,
                                             const int py,
                                             const int n_index)
{
  /* NOTE: Current method only uses polygon edges to detect neighboring pixels.
   *       -> It doesn't always lead to the optimum pixel but is accurate enough
   *          and faster/simpler than including possible face tip point links)
   */

  /* shift position by given n_index */
  const int x = px + neighX[n_index];
  const int y = py + neighY[n_index];

  if (x < 0 || x >= w || y < 0 || y >= h) {
    return OUT_OF_TEXTURE;
  }

  const PaintUVPoint *tempPoints = data->tempPoints;
  const PaintUVPoint *tPoint = &tempPoints[x + w * y];   /* UV neighbor */
  const PaintUVPoint *cPoint = &tempPoints[px + w * py]; /* Origin point */

  /* Check if shifted point is on same face -> it's a correct neighbor
   * (and if it isn't marked as an "edge pixel") */
  if ((tPoint->tri_index == cPoint->tri_index) && (tPoint->neighbor_pixel == -1)) {
    return (x + w * y);
  }

  /* Even if shifted point is on another face
   * -> use this point.
   *
   * !! Replace with "is uv faces linked" check !!
   * This should work fine as long as uv island margin is > 1 pixel.
   */
  if ((tPoint->tri_index != -1) && (tPoint->neighbor_pixel == -1)) {
    return (x + w * y);
  }

  /* If we get here, the actual neighboring pixel is located on a non-linked uv face,
   * and we have to find its "real" position.
   *
   * Simple neighboring face finding algorithm:
   *   - find closest uv edge to shifted pixel and get the another face that shares that edge
   *   - find corresponding position of that new face edge in uv space
   *
   * TODO: Implement something more accurate / optimized?
   */
  {
    DynamicPaintFindIslandBorderData bdata = {
        .vert_to_looptri_map = vert_to_looptri_map,
        .w = w,
        .h = h,
        .px = px,
        .py = py,
        .best_index = NOT_FOUND,
        .best_weight = 1.0f,
    };

    float pixel[2];

    pixel[0] = ((float)(px + neighX[n_index]) + 0.5f) / (float)w;
    pixel[1] = ((float)(py + neighY[n_index]) + 0.5f) / (float)h;

    /* Do a small recursive search for the best island edge. */
    dynamic_paint_find_island_border(data, &bdata, cPoint->tri_index, pixel, -1, 5);

    return bdata.best_index;
  }
}

static void dynamic_paint_find_island_border(const DynamicPaintCreateUVSurfaceData *data,
                                             DynamicPaintFindIslandBorderData *bdata,
                                             int tri_index,
                                             const float pixel[2],
                                             int in_edge,
                                             int depth)
{
  const MLoop *mloop = data->mloop;
  const MLoopTri *mlooptri = data->mlooptri;
  const MLoopUV *mloopuv = data->mloopuv;

  const unsigned int *loop_idx = mlooptri[tri_index].tri;

  /* Enumerate all edges of the triangle, rotating the vertex list accordingly. */
  for (int edge_idx = 0; edge_idx < 3; edge_idx++) {
    /* but not the edge we have just recursed through */
    if (edge_idx == in_edge) {
      continue;
    }

    float uv0[2], uv1[2], uv2[2];

    copy_v2_v2(uv0, mloopuv[loop_idx[(edge_idx + 0)]].uv);
    copy_v2_v2(uv1, mloopuv[loop_idx[(edge_idx + 1) % 3]].uv);
    copy_v2_v2(uv2, mloopuv[loop_idx[(edge_idx + 2) % 3]].uv);

    /* Verify the target point is on the opposite side of the edge from the third triangle
     * vertex, to ensure that we always move closer to the goal point. */
    const float sidep = line_point_side_v2(uv0, uv1, pixel);
    const float side2 = line_point_side_v2(uv0, uv1, uv2);

    if (side2 == 0.0f) {
      continue;
    }

    /* Hack: allow all edges of the original triangle */
    const bool correct_side = (in_edge == -1) || (sidep < 0 && side2 > 0) ||
                              (sidep > 0 && side2 < 0);

    /* Allow exactly on edge for the non-recursive case */
    if (!correct_side && sidep != 0.0f) {
      continue;
    }

    /* Now find another face that is linked to that edge. */
    const int vert0 = mloop[loop_idx[(edge_idx + 0)]].v;
    const int vert1 = mloop[loop_idx[(edge_idx + 1) % 3]].v;

    /* Use a pre-computed vert-to-looptri mapping,
     * speeds up things a lot compared to looping over all loopti. */
    const MeshElemMap *map = &bdata->vert_to_looptri_map[vert0];

    bool found_other = false;
    int target_tri = -1;
    int target_edge = -1;

    float ouv0[2], ouv1[2];

    for (int i = 0; i < map->count && !found_other; i++) {
      const int lt_index = map->indices[i];

      if (lt_index == tri_index) {
        continue;
      }

      const unsigned int *other_loop_idx = mlooptri[lt_index].tri;

      /* Check edges for match, looping in the same order as the outer loop. */
      for (int j = 0; j < 3; j++) {
        const int overt0 = mloop[other_loop_idx[(j + 0)]].v;
        const int overt1 = mloop[other_loop_idx[(j + 1) % 3]].v;

        /* Allow for swapped vertex order */
        if (overt0 == vert0 && overt1 == vert1) {
          found_other = true;
          copy_v2_v2(ouv0, mloopuv[other_loop_idx[(j + 0)]].uv);
          copy_v2_v2(ouv1, mloopuv[other_loop_idx[(j + 1) % 3]].uv);
        }
        else if (overt0 == vert1 && overt1 == vert0) {
          found_other = true;
          copy_v2_v2(ouv1, mloopuv[other_loop_idx[(j + 0)]].uv);
          copy_v2_v2(ouv0, mloopuv[other_loop_idx[(j + 1) % 3]].uv);
        }

        if (found_other) {
          target_tri = lt_index;
          target_edge = j;
          break;
        }
      }
    }

    if (!found_other) {
      if (bdata->best_index < 0) {
        bdata->best_index = ON_MESH_EDGE;
      }

      continue;
    }

    /* If this edge is connected in UV space too, recurse */
    if (equals_v2v2(uv0, ouv0) && equals_v2v2(uv1, ouv1)) {
      if (depth > 0 && correct_side) {
        dynamic_paint_find_island_border(data, bdata, target_tri, pixel, target_edge, depth - 1);
      }

      continue;
    }

    /* Otherwise try to map to the other side of the edge.
     * First check if there already is a better solution. */
    const float dist_squared = dist_squared_to_line_segment_v2(pixel, uv0, uv1);

    if (bdata->best_index >= 0 && dist_squared >= bdata->best_weight) {
      continue;
    }

    /*
     * Find a point that is relatively at same edge position
     * on this other face UV
     */
    float closest_point[2], dir_vec[2], tgt_pixel[2];

    float lambda = closest_to_line_v2(closest_point, pixel, uv0, uv1);
    CLAMP(lambda, 0.0f, 1.0f);

    sub_v2_v2v2(dir_vec, ouv1, ouv0);
    madd_v2_v2v2fl(tgt_pixel, ouv0, dir_vec, lambda);

    int w = bdata->w, h = bdata->h, px = bdata->px, py = bdata->py;

    const int final_pixel[2] = {(int)floorf(tgt_pixel[0] * w), (int)floorf(tgt_pixel[1] * h)};

    /* If current pixel uv is outside of texture */
    if (final_pixel[0] < 0 || final_pixel[0] >= w || final_pixel[1] < 0 || final_pixel[1] >= h) {
      if (bdata->best_index == NOT_FOUND) {
        bdata->best_index = OUT_OF_TEXTURE;
      }

      continue;
    }

    const PaintUVPoint *tempPoints = data->tempPoints;
    int final_index = final_pixel[0] + w * final_pixel[1];

    /* If we ended up to our origin point ( mesh has smaller than pixel sized faces) */
    if (final_index == (px + w * py)) {
      continue;
    }

    /* If final point is an "edge pixel", use its "real" neighbor instead */
    if (tempPoints[final_index].neighbor_pixel != -1) {
      final_index = tempPoints[final_index].neighbor_pixel;

      /* If we ended up to our origin point */
      if (final_index == (px + w * py)) {
        continue;
      }
    }

    const int final_tri_index = tempPoints[final_index].tri_index;
    /* If found pixel still lies on wrong face ( mesh has smaller than pixel sized faces) */
    if (!ELEM(final_tri_index, target_tri, -1)) {
      /* Check if it's close enough to likely touch the intended triangle. Any triangle
       * becomes thinner than a pixel at its vertices, so robustness requires some margin. */
      const float final_pt[2] = {((final_index % w) + 0.5f) / w, ((final_index / w) + 0.5f) / h};
      const float threshold = square_f(0.7f) / (w * h);

      if (dist_squared_to_looptri_uv_edges(mlooptri, mloopuv, final_tri_index, final_pt) >
          threshold) {
        continue;
      }
    }

    bdata->best_index = final_index;
    bdata->best_weight = dist_squared;
  }
}

static bool dynamicPaint_pointHasNeighbor(PaintAdjData *ed, int index, int neighbor)
{
  const int idx = ed->n_index[index];

  for (int i = 0; i < ed->n_num[index]; i++) {
    if (ed->n_target[idx + i] == neighbor) {
      return true;
    }
  }

  return false;
}

/* Makes the adjacency data symmetric, except for border pixels.
 * I.e. if A is neighbor of B, B is neighbor of A. */
static bool dynamicPaint_symmetrizeAdjData(PaintAdjData *ed, int active_points)
{
  int *new_n_index = MEM_callocN(sizeof(int) * active_points, "Surface Adj Index");
  int *new_n_num = MEM_callocN(sizeof(int) * active_points, "Surface Adj Counts");

  if (new_n_num && new_n_index) {
    /* Count symmetrized neighbors */
    int total_targets = 0;

    for (int index = 0; index < active_points; index++) {
      total_targets += ed->n_num[index];
      new_n_num[index] = ed->n_num[index];
    }

    for (int index = 0; index < active_points; index++) {
      if (ed->flags[index] & ADJ_BORDER_PIXEL) {
        continue;
      }

      for (int i = 0, idx = ed->n_index[index]; i < ed->n_num[index]; i++) {
        const int target = ed->n_target[idx + i];

        BLI_assert(!(ed->flags[target] & ADJ_BORDER_PIXEL));

        if (!dynamicPaint_pointHasNeighbor(ed, target, index)) {
          new_n_num[target]++;
          total_targets++;
        }
      }
    }

    /* Allocate a new target map */
    int *new_n_target = MEM_callocN(sizeof(int) * total_targets, "Surface Adj Targets");

    if (new_n_target) {
      /* Copy existing neighbors to the new map */
      int n_pos = 0;

      for (int index = 0; index < active_points; index++) {
        new_n_index[index] = n_pos;
        memcpy(&new_n_target[n_pos],
               &ed->n_target[ed->n_index[index]],
               sizeof(int) * ed->n_num[index]);

        /* Reset count to old, but advance position by new, leaving a gap to fill below. */
        n_pos += new_n_num[index];
        new_n_num[index] = ed->n_num[index];
      }

      BLI_assert(n_pos == total_targets);

      /* Add symmetrized - this loop behavior must exactly match the count pass above */
      for (int index = 0; index < active_points; index++) {
        if (ed->flags[index] & ADJ_BORDER_PIXEL) {
          continue;
        }

        for (int i = 0, idx = ed->n_index[index]; i < ed->n_num[index]; i++) {
          const int target = ed->n_target[idx + i];

          if (!dynamicPaint_pointHasNeighbor(ed, target, index)) {
            const int num = new_n_num[target]++;
            new_n_target[new_n_index[target] + num] = index;
          }
        }
      }

      /* Swap maps */
      MEM_freeN(ed->n_target);
      ed->n_target = new_n_target;

      MEM_freeN(ed->n_index);
      ed->n_index = new_n_index;

      MEM_freeN(ed->n_num);
      ed->n_num = new_n_num;

      ed->total_targets = total_targets;
      return true;
    }
  }

  if (new_n_index) {
    MEM_freeN(new_n_index);
  }
  if (new_n_num) {
    MEM_freeN(new_n_num);
  }

  return false;
}

int dynamicPaint_createUVSurface(Scene *scene,
                                 DynamicPaintSurface *surface,
                                 float *progress,
                                 short *do_update)
{
  /* Antialias jitter point relative coords */
  const int aa_samples = (surface->flags & MOD_DPAINT_ANTIALIAS) ? 5 : 1;
  char uvname[MAX_CUSTOMDATA_LAYER_NAME];
  uint32_t active_points = 0;
  bool error = false;

  PaintSurfaceData *sData;
  DynamicPaintCanvasSettings *canvas = surface->canvas;
  Mesh *mesh = dynamicPaint_canvas_mesh_get(canvas);

  PaintUVPoint *tempPoints = NULL;
  Vec3f *tempWeights = NULL;
  const MLoopTri *mlooptri = NULL;
  const MLoopUV *mloopuv = NULL;
  const MLoop *mloop = NULL;

  Bounds2D *faceBB = NULL;
  int *final_index;

  *progress = 0.0f;
  *do_update = true;

  if (!mesh) {
    return setError(canvas, N_("Canvas mesh not updated"));
  }
  if (surface->format != MOD_DPAINT_SURFACE_F_IMAGESEQ) {
    return setError(canvas, N_("Cannot bake non-'image sequence' formats"));
  }

  mloop = mesh->mloop;
  mlooptri = BKE_mesh_runtime_looptri_ensure(mesh);
  const int tottri = BKE_mesh_runtime_looptri_len(mesh);

  /* get uv map */
  if (CustomData_has_layer(&mesh->ldata, CD_MLOOPUV)) {
    CustomData_validate_layer_name(&mesh->ldata, CD_MLOOPUV, surface->uvlayer_name, uvname);
    mloopuv = CustomData_get_layer_named(&mesh->ldata, CD_MLOOPUV, uvname);
  }

  /* Check for validity */
  if (!mloopuv) {
    return setError(canvas, N_("No UV data on canvas"));
  }
  if (surface->image_resolution < 16 || surface->image_resolution > 8192) {
    return setError(canvas, N_("Invalid resolution"));
  }

  const int w = surface->image_resolution;
  const int h = w;

  /*
   * Start generating the surface
   */
  CLOG_INFO(&LOG, 1, "Preparing UV surface of %ix%i pixels and %i tris.", w, h, tottri);

  /* Init data struct */
  if (surface->data) {
    dynamicPaint_freeSurfaceData(surface);
  }
  sData = surface->data = MEM_callocN(sizeof(PaintSurfaceData), "PaintSurfaceData");
  if (!surface->data) {
    return setError(canvas, N_("Not enough free memory"));
  }

  tempPoints = MEM_callocN(w * h * sizeof(*tempPoints), "Temp PaintUVPoint");
  if (!tempPoints) {
    error = true;
  }

  final_index = MEM_callocN(w * h * sizeof(*final_index), "Temp UV Final Indexes");
  if (!final_index) {
    error = true;
  }

  tempWeights = MEM_mallocN(w * h * aa_samples * sizeof(*tempWeights), "Temp bWeights");
  if (!tempWeights) {
    error = true;
  }

  /*
   * Generate a temporary bounding box array for UV faces to optimize
   * the pixel-inside-a-face search.
   */
  if (!error) {
    faceBB = MEM_mallocN(tottri * sizeof(*faceBB), "MPCanvasFaceBB");
    if (!faceBB) {
      error = true;
    }
  }

  *progress = 0.01f;
  *do_update = true;

  if (!error) {
    for (int i = 0; i < tottri; i++) {
      copy_v2_v2(faceBB[i].min, mloopuv[mlooptri[i].tri[0]].uv);
      copy_v2_v2(faceBB[i].max, mloopuv[mlooptri[i].tri[0]].uv);

      for (int j = 1; j < 3; j++) {
        minmax_v2v2_v2(faceBB[i].min, faceBB[i].max, mloopuv[mlooptri[i].tri[j]].uv);
      }
    }

    *progress = 0.02f;
    *do_update = true;

    /* Loop through every pixel and check if pixel is uv-mapped on a canvas face. */
    DynamicPaintCreateUVSurfaceData data = {
        .surface = surface,
        .tempPoints = tempPoints,
        .tempWeights = tempWeights,
        .mlooptri = mlooptri,
        .mloopuv = mloopuv,
        .mloop = mloop,
        .tottri = tottri,
        .faceBB = faceBB,
    };
    {
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (h > 64 || tottri > 1000);
      BLI_task_parallel_range(0, h, &data, dynamic_paint_create_uv_surface_direct_cb, &settings);
    }

    *progress = 0.04f;
    *do_update = true;

    /*
     * Now loop through every pixel that was left without index
     * and find if they have neighboring pixels that have an index.
     * If so use that polygon as pixel surface.
     * (To avoid seams on uv island edges)
     */
    data.active_points = &active_points;
    {
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (h > 64);
      BLI_task_parallel_range(0, h, &data, dynamic_paint_create_uv_surface_neighbor_cb, &settings);
    }

    *progress = 0.06f;
    *do_update = true;

    /* Generate surface adjacency data. */
    {
      int cursor = 0;

      /* Create a temporary array of final indexes (before unassigned
       * pixels have been dropped) */
      for (int i = 0; i < w * h; i++) {
        if (tempPoints[i].tri_index != -1) {
          final_index[i] = cursor;
          cursor++;
        }
      }
      /* allocate memory */
      sData->total_points = w * h;
      dynamicPaint_initAdjacencyData(surface, true);

      if (sData->adj_data) {
        PaintAdjData *ed = sData->adj_data;
        int n_pos = 0;

        MeshElemMap *vert_to_looptri_map;
        int *vert_to_looptri_map_mem;

        BKE_mesh_vert_looptri_map_create(&vert_to_looptri_map,
                                         &vert_to_looptri_map_mem,
                                         mesh->mvert,
                                         mesh->totvert,
                                         mlooptri,
                                         tottri,
                                         mloop,
                                         mesh->totloop);

        int total_border = 0;

        for (int ty = 0; ty < h; ty++) {
          for (int tx = 0; tx < w; tx++) {
            const int index = tx + w * ty;

            if (tempPoints[index].tri_index != -1) {
              ed->n_index[final_index[index]] = n_pos;
              ed->n_num[final_index[index]] = 0;

              if (tempPoints[index].neighbor_pixel != -1) {
                ed->flags[final_index[index]] |= ADJ_BORDER_PIXEL;
                total_border++;
              }

              for (int i = 0; i < 8; i++) {
                /* Try to find a neighboring pixel in defined direction.
                 * If not found, -1 is returned */
                const int n_target = dynamic_paint_find_neighbor_pixel(
                    &data, vert_to_looptri_map, w, h, tx, ty, i);

                if (n_target >= 0 && n_target != index) {
                  if (!dynamicPaint_pointHasNeighbor(
                          ed, final_index[index], final_index[n_target])) {
                    ed->n_target[n_pos] = final_index[n_target];
                    ed->n_num[final_index[index]]++;
                    n_pos++;
                  }
                }
                else if (ELEM(n_target, ON_MESH_EDGE, OUT_OF_TEXTURE)) {
                  ed->flags[final_index[index]] |= ADJ_ON_MESH_EDGE;
                }
              }
            }
          }
        }

        MEM_freeN(vert_to_looptri_map);
        MEM_freeN(vert_to_looptri_map_mem);

        /* Make neighbors symmetric */
        if (!dynamicPaint_symmetrizeAdjData(ed, active_points)) {
          error = true;
        }

        /* Create a list of border pixels */
        ed->border = MEM_callocN(sizeof(int) * total_border, "Border Pixel Index");

        if (ed->border) {
          ed->total_border = total_border;

          for (int i = 0, next = 0; i < active_points; i++) {
            if (ed->flags[i] & ADJ_BORDER_PIXEL) {
              ed->border[next++] = i;
            }
          }
        }

#if 0
        /* -----------------------------------------------------------------
         * For debug, write a dump of adjacency data to a file.
         * -----------------------------------------------------------------*/
        FILE *dump_file = fopen("dynpaint-adj-data.txt", "w");
        int *tmp = MEM_callocN(sizeof(int) * active_points, "tmp");
        for (int ty = 0; ty < h; ty++) {
          for (int tx = 0; tx < w; tx++) {
            const int index = tx + w * ty;
            if (tempPoints[index].tri_index != -1) {
              tmp[final_index[index]] = index;
            }
          }
        }
        for (int ty = 0; ty < h; ty++) {
          for (int tx = 0; tx < w; tx++) {
            const int index = tx + w * ty;
            const int fidx = final_index[index];

            if (tempPoints[index].tri_index != -1) {
              int nidx = tempPoints[index].neighbor_pixel;
              fprintf(dump_file,
                      "%d\t%d,%d\t%u\t%d,%d\t%d\t",
                      fidx,
                      tx,
                      h - 1 - ty,
                      tempPoints[index].tri_index,
                      nidx < 0 ? -1 : (nidx % w),
                      nidx < 0 ? -1 : h - 1 - (nidx / w),
                      ed->flags[fidx]);
              for (int i = 0; i < ed->n_num[fidx]; i++) {
                int tgt = tmp[ed->n_target[ed->n_index[fidx] + i]];
                fprintf(dump_file, "%s%d,%d", i ? " " : "", tgt % w, h - 1 - tgt / w);
              }
              fprintf(dump_file, "\n");
            }
          }
        }
        MEM_freeN(tmp);
        fclose(dump_file);
#endif
      }
    }

    *progress = 0.08f;
    *do_update = true;

    /* Create final surface data without inactive points */
    ImgSeqFormatData *f_data = MEM_callocN(sizeof(*f_data), "ImgSeqFormatData");
    if (f_data) {
      f_data->uv_p = MEM_callocN(active_points * sizeof(*f_data->uv_p), "PaintUVPoint");
      f_data->barycentricWeights = MEM_callocN(
          active_points * aa_samples * sizeof(*f_data->barycentricWeights), "PaintUVPoint");

      if (!f_data->uv_p || !f_data->barycentricWeights) {
        error = 1;
      }
    }
    else {
      error = 1;
    }

    /* in case of allocation error, free everything */
    if (error) {
      if (f_data) {
        if (f_data->uv_p) {
          MEM_freeN(f_data->uv_p);
        }
        if (f_data->barycentricWeights) {
          MEM_freeN(f_data->barycentricWeights);
        }
        MEM_freeN(f_data);
      }
      sData->total_points = 0;
    }
    else {
      sData->total_points = (int)active_points;
      sData->format_data = f_data;

      for (int index = 0, cursor = 0; index < (w * h); index++) {
        if (tempPoints[index].tri_index != -1) {
          memcpy(&f_data->uv_p[cursor], &tempPoints[index], sizeof(PaintUVPoint));
          memcpy(&f_data->barycentricWeights[cursor * aa_samples],
                 &tempWeights[index * aa_samples],
                 sizeof(*tempWeights) * aa_samples);
          cursor++;
        }
      }
    }
  }
  if (error == 1) {
    setError(canvas, N_("Not enough free memory"));
  }

  if (faceBB) {
    MEM_freeN(faceBB);
  }
  if (tempPoints) {
    MEM_freeN(tempPoints);
  }
  if (tempWeights) {
    MEM_freeN(tempWeights);
  }
  if (final_index) {
    MEM_freeN(final_index);
  }

  /* Init surface type data */
  if (!error) {
    dynamicPaint_allocateSurfaceType(surface);

#if 0
    /* -----------------------------------------------------------------
     * For debug, output pixel statuses to the color map
     * -----------------------------------------------------------------*/
    for (index = 0; index < sData->total_points; index++) {
      ImgSeqFormatData *f_data = (ImgSeqFormatData *)sData->format_data;
      PaintUVPoint *uvPoint = &((PaintUVPoint *)f_data->uv_p)[index];
      PaintPoint *pPoint = &((PaintPoint *)sData->type_data)[index];
      pPoint->alpha = 1.0f;

      /* Every pixel that is assigned as "edge pixel" gets blue color */
      if (uvPoint->neighbor_pixel != -1) {
        pPoint->color[2] = 1.0f;
      }
      /* and every pixel that finally got an polygon gets red color */
      /* green color shows pixel face index hash */
      if (uvPoint->tri_index != -1) {
        pPoint->color[0] = 1.0f;
        pPoint->color[1] = (float)(uvPoint->tri_index % 255) / 256.0f;
      }
    }
#endif

    dynamicPaint_setInitialColor(scene, surface);
  }

  *progress = 0.09f;
  *do_update = true;

  return (error == 0);
}

/*
 * Outputs an image file from uv surface data.
 */
typedef struct DynamicPaintOutputSurfaceImageData {
  const DynamicPaintSurface *surface;
  ImBuf *ibuf;
} DynamicPaintOutputSurfaceImageData;

static void dynamic_paint_output_surface_image_paint_cb(
    void *__restrict userdata, const int index, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintOutputSurfaceImageData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintPoint *point = &((PaintPoint *)surface->data->type_data)[index];

  ImBuf *ibuf = data->ibuf;
  /* image buffer position */
  const int pos = ((ImgSeqFormatData *)(surface->data->format_data))->uv_p[index].pixel_index * 4;

  /* blend wet and dry layers */
  blendColors(
      point->color, point->color[3], point->e_color, point->e_color[3], &ibuf->rect_float[pos]);

  /* Multiply color by alpha if enabled */
  if (surface->flags & MOD_DPAINT_MULALPHA) {
    mul_v3_fl(&ibuf->rect_float[pos], ibuf->rect_float[pos + 3]);
  }
}

static void dynamic_paint_output_surface_image_displace_cb(
    void *__restrict userdata, const int index, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintOutputSurfaceImageData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  float depth = ((float *)surface->data->type_data)[index];

  ImBuf *ibuf = data->ibuf;
  /* image buffer position */
  const int pos = ((ImgSeqFormatData *)(surface->data->format_data))->uv_p[index].pixel_index * 4;

  if (surface->depth_clamp) {
    depth /= surface->depth_clamp;
  }

  if (surface->disp_type == MOD_DPAINT_DISP_DISPLACE) {
    depth = (0.5f - depth / 2.0f);
  }

  CLAMP(depth, 0.0f, 1.0f);

  copy_v3_fl(&ibuf->rect_float[pos], depth);
  ibuf->rect_float[pos + 3] = 1.0f;
}

static void dynamic_paint_output_surface_image_wave_cb(
    void *__restrict userdata, const int index, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintOutputSurfaceImageData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintWavePoint *wPoint = &((PaintWavePoint *)surface->data->type_data)[index];
  float depth = wPoint->height;

  ImBuf *ibuf = data->ibuf;
  /* image buffer position */
  const int pos = ((ImgSeqFormatData *)(surface->data->format_data))->uv_p[index].pixel_index * 4;

  if (surface->depth_clamp) {
    depth /= surface->depth_clamp;
  }

  depth = (0.5f + depth / 2.0f);
  CLAMP(depth, 0.0f, 1.0f);

  copy_v3_fl(&ibuf->rect_float[pos], depth);
  ibuf->rect_float[pos + 3] = 1.0f;
}

static void dynamic_paint_output_surface_image_wetmap_cb(
    void *__restrict userdata, const int index, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintOutputSurfaceImageData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintPoint *point = &((PaintPoint *)surface->data->type_data)[index];

  ImBuf *ibuf = data->ibuf;
  /* image buffer position */
  const int pos = ((ImgSeqFormatData *)(surface->data->format_data))->uv_p[index].pixel_index * 4;

  copy_v3_fl(&ibuf->rect_float[pos], (point->wetness > 1.0f) ? 1.0f : point->wetness);
  ibuf->rect_float[pos + 3] = 1.0f;
}

void dynamicPaint_outputSurfaceImage(DynamicPaintSurface *surface,
                                     char *filename,
                                     short output_layer)
{
  ImBuf *ibuf = NULL;
  PaintSurfaceData *sData = surface->data;
  /* OpenEXR or PNG */
  int format = (surface->image_fileformat & MOD_DPAINT_IMGFORMAT_OPENEXR) ? R_IMF_IMTYPE_OPENEXR :
                                                                            R_IMF_IMTYPE_PNG;
  char output_file[FILE_MAX];

  if (!sData->type_data) {
    setError(surface->canvas, N_("Image save failed: invalid surface"));
    return;
  }
  /* if selected format is openexr, but current build doesn't support one */
#ifndef WITH_OPENEXR
  if (format == R_IMF_IMTYPE_OPENEXR) {
    format = R_IMF_IMTYPE_PNG;
  }
#endif
  BLI_strncpy(output_file, filename, sizeof(output_file));
  BKE_image_path_ensure_ext_from_imtype(output_file, format);

  /* Validate output file path */
  BLI_path_abs(output_file, BKE_main_blendfile_path_from_global());
  BLI_make_existing_file(output_file);

  /* Init image buffer */
  ibuf = IMB_allocImBuf(surface->image_resolution, surface->image_resolution, 32, IB_rectfloat);
  if (ibuf == NULL) {
    setError(surface->canvas, N_("Image save failed: not enough free memory"));
    return;
  }

  DynamicPaintOutputSurfaceImageData data = {
      .surface = surface,
      .ibuf = ibuf,
  };
  switch (surface->type) {
    case MOD_DPAINT_SURFACE_T_PAINT:
      switch (output_layer) {
        case 0: {
          TaskParallelSettings settings;
          BLI_parallel_range_settings_defaults(&settings);
          settings.use_threading = (sData->total_points > 10000);
          BLI_task_parallel_range(0,
                                  sData->total_points,
                                  &data,
                                  dynamic_paint_output_surface_image_paint_cb,
                                  &settings);
          break;
        }
        case 1: {
          TaskParallelSettings settings;
          BLI_parallel_range_settings_defaults(&settings);
          settings.use_threading = (sData->total_points > 10000);
          BLI_task_parallel_range(0,
                                  sData->total_points,
                                  &data,
                                  dynamic_paint_output_surface_image_wetmap_cb,
                                  &settings);
          break;
        }
        default:
          BLI_assert(0);
          break;
      }
      break;
    case MOD_DPAINT_SURFACE_T_DISPLACE:
      switch (output_layer) {
        case 0: {
          TaskParallelSettings settings;
          BLI_parallel_range_settings_defaults(&settings);
          settings.use_threading = (sData->total_points > 10000);
          BLI_task_parallel_range(0,
                                  sData->total_points,
                                  &data,
                                  dynamic_paint_output_surface_image_displace_cb,
                                  &settings);
          break;
        }
        case 1:
          break;
        default:
          BLI_assert(0);
          break;
      }
      break;
    case MOD_DPAINT_SURFACE_T_WAVE:
      switch (output_layer) {
        case 0: {
          TaskParallelSettings settings;
          BLI_parallel_range_settings_defaults(&settings);
          settings.use_threading = (sData->total_points > 10000);
          BLI_task_parallel_range(0,
                                  sData->total_points,
                                  &data,
                                  dynamic_paint_output_surface_image_wave_cb,
                                  &settings);
          break;
        }
        case 1:
          break;
        default:
          BLI_assert(0);
          break;
      }
      break;
    default:
      BLI_assert(0);
      break;
  }

    /* Set output format, PNG in case EXR isn't supported. */
#ifdef WITH_OPENEXR
  if (format == R_IMF_IMTYPE_OPENEXR) { /* OpenEXR 32-bit float */
    ibuf->ftype = IMB_FTYPE_OPENEXR;
    ibuf->foptions.flag |= OPENEXR_COMPRESS;
  }
  else
#endif
  {
    ibuf->ftype = IMB_FTYPE_PNG;
    ibuf->foptions.quality = 15;
  }

  /* Save image */
  IMB_saveiff(ibuf, output_file, IB_rectfloat);
  IMB_freeImBuf(ibuf);
}

/** \} */

/***************************** Ray / Nearest Point Utils ******************************/

/* A modified callback to bvh tree ray-cast.
 * The tree must have been built using bvhtree_from_mesh_looptri.
 * userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree.
 *
 * To optimize brush detection speed this doesn't calculate hit coordinates or normal.
 */
static void mesh_tris_spherecast_dp(void *userdata,
                                    int index,
                                    const BVHTreeRay *ray,
                                    BVHTreeRayHit *hit)
{
  const BVHTreeFromMesh *data = (BVHTreeFromMesh *)userdata;
  const MVert *vert = data->vert;
  const MLoopTri *mlooptri = data->looptri;
  const MLoop *mloop = data->loop;

  const float *t0, *t1, *t2;
  float dist;

  t0 = vert[mloop[mlooptri[index].tri[0]].v].co;
  t1 = vert[mloop[mlooptri[index].tri[1]].v].co;
  t2 = vert[mloop[mlooptri[index].tri[2]].v].co;

  dist = bvhtree_ray_tri_intersection(ray, hit->dist, t0, t1, t2);

  if (dist >= 0 && dist < hit->dist) {
    hit->index = index;
    hit->dist = dist;
    hit->no[0] = 0.0f;
  }
}

/* A modified callback to bvh tree nearest point.
 * The tree must have been built using bvhtree_from_mesh_looptri.
 * userdata must be a BVHMeshCallbackUserdata built from the same mesh as the tree.
 *
 * To optimize brush detection speed this doesn't calculate hit normal.
 */
static void mesh_tris_nearest_point_dp(void *userdata,
                                       int index,
                                       const float co[3],
                                       BVHTreeNearest *nearest)
{
  const BVHTreeFromMesh *data = (BVHTreeFromMesh *)userdata;
  const MVert *vert = data->vert;
  const MLoopTri *mlooptri = data->looptri;
  const MLoop *mloop = data->loop;
  float nearest_tmp[3], dist_sq;

  const float *t0, *t1, *t2;
  t0 = vert[mloop[mlooptri[index].tri[0]].v].co;
  t1 = vert[mloop[mlooptri[index].tri[1]].v].co;
  t2 = vert[mloop[mlooptri[index].tri[2]].v].co;

  closest_on_tri_to_point_v3(nearest_tmp, co, t0, t1, t2);
  dist_sq = len_squared_v3v3(co, nearest_tmp);

  if (dist_sq < nearest->dist_sq) {
    nearest->index = index;
    nearest->dist_sq = dist_sq;
    copy_v3_v3(nearest->co, nearest_tmp);
    nearest->no[0] = 0.0f;
  }
}

/***************************** Brush Painting Calls ******************************/

/**
 * Mix color values to canvas point.
 *
 * \param surface: Canvas surface
 * \param index: Surface point index
 * \param paintFlags: paint object flags
 * \param paintColor,paintAlpha,paintWetness: To be mixed paint values
 * \param timescale: Value used to adjust time dependent
 * operations when using substeps
 */
static void dynamicPaint_mixPaintColors(const DynamicPaintSurface *surface,
                                        const int index,
                                        const int paintFlags,
                                        const float paintColor[3],
                                        const float paintAlpha,
                                        const float paintWetness,
                                        const float timescale)
{
  PaintPoint *pPoint = &((PaintPoint *)surface->data->type_data)[index];

  /* Add paint */
  if (!(paintFlags & MOD_DPAINT_ERASE)) {
    float mix[4];
    float temp_alpha = paintAlpha * ((paintFlags & MOD_DPAINT_ABS_ALPHA) ? 1.0f : timescale);

    /* mix brush color with wet layer color */
    blendColors(pPoint->e_color, pPoint->e_color[3], paintColor, temp_alpha, mix);
    copy_v3_v3(pPoint->e_color, mix);

    /* mix wetness and alpha depending on selected alpha mode */
    if (paintFlags & MOD_DPAINT_ABS_ALPHA) {
      /* update values to the brush level unless they're higher already */
      CLAMP_MIN(pPoint->e_color[3], paintAlpha);
      CLAMP_MIN(pPoint->wetness, paintWetness);
    }
    else {
      float wetness = paintWetness;
      CLAMP(wetness, 0.0f, 1.0f);
      pPoint->e_color[3] = mix[3];
      pPoint->wetness = pPoint->wetness * (1.0f - wetness) + wetness;
    }

    CLAMP_MIN(pPoint->wetness, MIN_WETNESS);

    pPoint->state = DPAINT_PAINT_NEW;
  }
  /* Erase paint */
  else {
    float a_ratio, a_highest;
    float wetness;
    float invFact = 1.0f - paintAlpha;

    /*
     * Make highest alpha to match erased value
     * but maintain alpha ratio
     */
    if (paintFlags & MOD_DPAINT_ABS_ALPHA) {
      a_highest = max_ff(pPoint->color[3], pPoint->e_color[3]);
      if (a_highest > invFact) {
        a_ratio = invFact / a_highest;

        pPoint->e_color[3] *= a_ratio;
        pPoint->color[3] *= a_ratio;
      }
    }
    else {
      pPoint->e_color[3] -= paintAlpha * timescale;
      CLAMP_MIN(pPoint->e_color[3], 0.0f);
      pPoint->color[3] -= paintAlpha * timescale;
      CLAMP_MIN(pPoint->color[3], 0.0f);
    }

    wetness = (1.0f - paintWetness) * pPoint->e_color[3];
    CLAMP_MAX(pPoint->wetness, wetness);
  }
}

/* applies given brush intersection value for wave surface */
static void dynamicPaint_mixWaveHeight(PaintWavePoint *wPoint,
                                       const DynamicPaintBrushSettings *brush,
                                       float isect_height)
{
  const float isect_change = isect_height - wPoint->brush_isect;
  const float wave_factor = brush->wave_factor;
  bool hit = false;

  /* intersection marked regardless of brush type or hit */
  wPoint->brush_isect = isect_height;
  wPoint->state = DPAINT_WAVE_ISECT_CHANGED;

  isect_height *= wave_factor;

  /* determine hit depending on wave_factor */
  if (wave_factor > 0.0f && wPoint->height > isect_height) {
    hit = true;
  }
  else if (wave_factor < 0.0f && wPoint->height < isect_height) {
    hit = true;
  }

  if (hit) {
    switch (brush->wave_type) {
      case MOD_DPAINT_WAVEB_DEPTH:
        wPoint->height = isect_height;
        wPoint->state = DPAINT_WAVE_OBSTACLE;
        wPoint->velocity = 0.0f;
        break;
      case MOD_DPAINT_WAVEB_FORCE:
        wPoint->velocity = isect_height;
        break;
      case MOD_DPAINT_WAVEB_REFLECT:
        wPoint->state = DPAINT_WAVE_REFLECT_ONLY;
        break;
      case MOD_DPAINT_WAVEB_CHANGE:
        if (isect_change < 0.0f) {
          wPoint->height += isect_change * wave_factor;
        }
        break;
      default:
        BLI_assert(0);
        break;
    }
  }
}

/*
 * add brush results to the surface data depending on surface type
 */
static void dynamicPaint_updatePointData(const DynamicPaintSurface *surface,
                                         const int index,
                                         const DynamicPaintBrushSettings *brush,
                                         float paint[3],
                                         float influence,
                                         float depth,
                                         float vel_factor,
                                         const float timescale)
{
  PaintSurfaceData *sData = surface->data;
  float strength;

  /* apply influence scale */
  influence *= surface->influence_scale;
  depth *= surface->influence_scale;

  strength = influence * brush->alpha;
  CLAMP(strength, 0.0f, 1.0f);

  /* Sample velocity colorband if required */
  if (brush->flags &
      (MOD_DPAINT_VELOCITY_ALPHA | MOD_DPAINT_VELOCITY_COLOR | MOD_DPAINT_VELOCITY_DEPTH)) {
    float coba_res[4];
    vel_factor /= brush->max_velocity;
    CLAMP(vel_factor, 0.0f, 1.0f);

    if (BKE_colorband_evaluate(brush->vel_ramp, vel_factor, coba_res)) {
      if (brush->flags & MOD_DPAINT_VELOCITY_COLOR) {
        copy_v3_v3(paint, coba_res);
      }
      if (brush->flags & MOD_DPAINT_VELOCITY_ALPHA) {
        strength *= coba_res[3];
      }
      if (brush->flags & MOD_DPAINT_VELOCITY_DEPTH) {
        depth *= coba_res[3];
      }
    }
  }

  /* mix paint surface */
  if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
    float paintWetness = brush->wetness * strength;
    float paintAlpha = strength;

    dynamicPaint_mixPaintColors(
        surface, index, brush->flags, paint, paintAlpha, paintWetness, timescale);
  }
  /* displace surface */
  else if (surface->type == MOD_DPAINT_SURFACE_T_DISPLACE) {
    float *value = (float *)sData->type_data;

    if (surface->flags & MOD_DPAINT_DISP_INCREMENTAL) {
      depth = value[index] + depth;
    }

    if (surface->depth_clamp) {
      CLAMP(depth, 0.0f - surface->depth_clamp, surface->depth_clamp);
    }

    if (brush->flags & MOD_DPAINT_ERASE) {
      value[index] *= (1.0f - strength);
      CLAMP_MIN(value[index], 0.0f);
    }
    else {
      CLAMP_MIN(value[index], depth);
    }
  }
  /* vertex weight group surface */
  else if (surface->type == MOD_DPAINT_SURFACE_T_WEIGHT) {
    float *value = (float *)sData->type_data;

    if (brush->flags & MOD_DPAINT_ERASE) {
      value[index] *= (1.0f - strength);
      CLAMP_MIN(value[index], 0.0f);
    }
    else {
      CLAMP_MIN(value[index], strength);
    }
  }
  /* wave surface */
  else if (surface->type == MOD_DPAINT_SURFACE_T_WAVE) {
    if (brush->wave_clamp) {
      CLAMP(depth, 0.0f - brush->wave_clamp, brush->wave_clamp);
    }

    dynamicPaint_mixWaveHeight(&((PaintWavePoint *)sData->type_data)[index], brush, 0.0f - depth);
  }

  /* doing velocity based painting */
  if (sData->bData->brush_velocity) {
    sData->bData->brush_velocity[index * 4 + 3] *= influence;
  }
}

/* checks whether surface and brush bounds intersect depending on brush type */
static bool meshBrush_boundsIntersect(Bounds3D *b1,
                                      Bounds3D *b2,
                                      DynamicPaintBrushSettings *brush,
                                      float brush_radius)
{
  if (brush->collision == MOD_DPAINT_COL_VOLUME) {
    return boundsIntersect(b1, b2);
  }
  if (ELEM(brush->collision, MOD_DPAINT_COL_DIST, MOD_DPAINT_COL_VOLDIST)) {
    return boundsIntersectDist(b1, b2, brush_radius);
  }
  return true;
}

/* calculate velocity for mesh vertices */
typedef struct DynamicPaintBrushVelocityData {
  Vec3f *brush_vel;

  const MVert *mvert_p;
  const MVert *mvert_c;

  float (*obmat)[4];
  float (*prev_obmat)[4];

  const float timescale;
} DynamicPaintBrushVelocityData;

static void dynamic_paint_brush_velocity_compute_cb(void *__restrict userdata,
                                                    const int i,
                                                    const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintBrushVelocityData *data = userdata;

  Vec3f *brush_vel = data->brush_vel;

  const MVert *mvert_p = data->mvert_p;
  const MVert *mvert_c = data->mvert_c;

  float(*obmat)[4] = data->obmat;
  float(*prev_obmat)[4] = data->prev_obmat;

  const float timescale = data->timescale;

  float p1[3], p2[3];

  copy_v3_v3(p1, mvert_p[i].co);
  mul_m4_v3(prev_obmat, p1);

  copy_v3_v3(p2, mvert_c[i].co);
  mul_m4_v3(obmat, p2);

  sub_v3_v3v3(brush_vel[i].v, p2, p1);
  mul_v3_fl(brush_vel[i].v, 1.0f / timescale);
}

static void dynamicPaint_brushMeshCalculateVelocity(Depsgraph *depsgraph,
                                                    Scene *scene,
                                                    Object *ob,
                                                    DynamicPaintBrushSettings *brush,
                                                    Vec3f **brushVel,
                                                    float timescale)
{
  float prev_obmat[4][4];
  Mesh *mesh_p, *mesh_c;
  MVert *mvert_p, *mvert_c;
  int numOfVerts_p, numOfVerts_c;

  float cur_sfra = scene->r.subframe;
  int cur_fra = scene->r.cfra;
  float prev_sfra = cur_sfra - timescale;
  int prev_fra = cur_fra;

  if (prev_sfra < 0.0f) {
    prev_sfra += 1.0f;
    prev_fra = cur_fra - 1;
  }

  /* previous frame mesh */
  scene->r.cfra = prev_fra;
  scene->r.subframe = prev_sfra;

  BKE_object_modifier_update_subframe(depsgraph,
                                      scene,
                                      ob,
                                      true,
                                      SUBFRAME_RECURSION,
                                      BKE_scene_ctime_get(scene),
                                      eModifierType_DynamicPaint);
  mesh_p = BKE_mesh_copy_for_eval(dynamicPaint_brush_mesh_get(brush), false);
  numOfVerts_p = mesh_p->totvert;
  mvert_p = mesh_p->mvert;
  copy_m4_m4(prev_obmat, ob->obmat);

  /* current frame mesh */
  scene->r.cfra = cur_fra;
  scene->r.subframe = cur_sfra;

  BKE_object_modifier_update_subframe(depsgraph,
                                      scene,
                                      ob,
                                      true,
                                      SUBFRAME_RECURSION,
                                      BKE_scene_ctime_get(scene),
                                      eModifierType_DynamicPaint);
  mesh_c = dynamicPaint_brush_mesh_get(brush);
  numOfVerts_c = mesh_c->totvert;
  mvert_c = mesh_c->mvert;

  (*brushVel) = (struct Vec3f *)MEM_mallocN(numOfVerts_c * sizeof(Vec3f),
                                            "Dynamic Paint brush velocity");
  if (!(*brushVel)) {
    return;
  }

  /* if mesh is constructive -> num of verts has changed, only use current frame derived mesh */
  if (numOfVerts_p != numOfVerts_c) {
    mvert_p = mvert_c;
  }

  /* calculate speed */
  DynamicPaintBrushVelocityData data = {
      .brush_vel = *brushVel,
      .mvert_p = mvert_p,
      .mvert_c = mvert_c,
      .obmat = ob->obmat,
      .prev_obmat = prev_obmat,
      .timescale = timescale,
  };
  TaskParallelSettings settings;
  BLI_parallel_range_settings_defaults(&settings);
  settings.use_threading = (numOfVerts_c > 10000);
  BLI_task_parallel_range(
      0, numOfVerts_c, &data, dynamic_paint_brush_velocity_compute_cb, &settings);

  BKE_id_free(NULL, mesh_p);
}

/* calculate velocity for object center point */
static void dynamicPaint_brushObjectCalculateVelocity(
    Depsgraph *depsgraph, Scene *scene, Object *ob, Vec3f *brushVel, float timescale)
{
  float prev_obmat[4][4];
  float cur_loc[3] = {0.0f}, prev_loc[3] = {0.0f};

  float cur_sfra = scene->r.subframe;
  int cur_fra = scene->r.cfra;
  float prev_sfra = cur_sfra - timescale;
  int prev_fra = cur_fra;

  if (prev_sfra < 0.0f) {
    prev_sfra += 1.0f;
    prev_fra = cur_fra - 1;
  }

  /* previous frame mesh */
  scene->r.cfra = prev_fra;
  scene->r.subframe = prev_sfra;
  BKE_object_modifier_update_subframe(depsgraph,
                                      scene,
                                      ob,
                                      false,
                                      SUBFRAME_RECURSION,
                                      BKE_scene_ctime_get(scene),
                                      eModifierType_DynamicPaint);
  copy_m4_m4(prev_obmat, ob->obmat);

  /* current frame mesh */
  scene->r.cfra = cur_fra;
  scene->r.subframe = cur_sfra;
  BKE_object_modifier_update_subframe(depsgraph,
                                      scene,
                                      ob,
                                      false,
                                      SUBFRAME_RECURSION,
                                      BKE_scene_ctime_get(scene),
                                      eModifierType_DynamicPaint);

  /* calculate speed */
  mul_m4_v3(prev_obmat, prev_loc);
  mul_m4_v3(ob->obmat, cur_loc);

  sub_v3_v3v3(brushVel->v, cur_loc, prev_loc);
  mul_v3_fl(brushVel->v, 1.0f / timescale);
}

typedef struct DynamicPaintPaintData {
  const DynamicPaintSurface *surface;
  const DynamicPaintBrushSettings *brush;
  Object *brushOb;
  const Scene *scene;
  const float timescale;
  const int c_index;

  Mesh *mesh;
  const MVert *mvert;
  const MLoop *mloop;
  const MLoopTri *mlooptri;
  const float brush_radius;
  const float *avg_brushNor;
  const Vec3f *brushVelocity;

  const ParticleSystem *psys;
  const float solidradius;

  void *treeData;

  float *pointCoord;
} DynamicPaintPaintData;

/*
 * Paint a brush object mesh to the surface
 */
static void dynamic_paint_paint_mesh_cell_point_cb_ex(
    void *__restrict userdata, const int id, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintPaintData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;
  const PaintBakeData *bData = sData->bData;
  VolumeGrid *grid = bData->grid;

  const DynamicPaintBrushSettings *brush = data->brush;

  const float timescale = data->timescale;
  const int c_index = data->c_index;

  const MVert *mvert = data->mvert;
  const MLoop *mloop = data->mloop;
  const MLoopTri *mlooptri = data->mlooptri;
  const float brush_radius = data->brush_radius;
  const float *avg_brushNor = data->avg_brushNor;
  const Vec3f *brushVelocity = data->brushVelocity;

  BVHTreeFromMesh *treeData = data->treeData;

  const int index = grid->t_index[grid->s_pos[c_index] + id];
  const int samples = bData->s_num[index];
  int ss;
  float total_sample = (float)samples;
  float brushStrength = 0.0f; /* brush influence factor */
  float depth = 0.0f;         /* brush intersection depth */
  float velocity_val = 0.0f;

  float paintColor[3] = {0.0f};
  int numOfHits = 0;

  /* for image sequence anti-aliasing, use gaussian factors */
  if (samples > 1 && surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
    total_sample = gaussianTotal;
  }

  /* Super-sampling */
  for (ss = 0; ss < samples; ss++) {
    float ray_start[3], ray_dir[3];
    float sample_factor = 0.0f;
    float sampleStrength = 0.0f;
    BVHTreeRayHit hit;
    BVHTreeNearest nearest;
    short hit_found = 0;

    /* volume sample */
    float volume_factor = 0.0f;
    /* proximity sample */
    float proximity_factor = 0.0f;
    float prox_colorband[4] = {0.0f};
    const bool inner_proximity = (brush->flags & MOD_DPAINT_INVERSE_PROX &&
                                  brush->collision == MOD_DPAINT_COL_VOLDIST);

    /* hit data */
    float hitCoord[3];
    int hitTri = -1;

    /* Super-sampling factor. */
    if (samples > 1 && surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
      sample_factor = gaussianFactors[ss];
    }
    else {
      sample_factor = 1.0f;
    }

    /* Get current sample position in world coordinates */
    copy_v3_v3(ray_start, bData->realCoord[bData->s_pos[index] + ss].v);
    copy_v3_v3(ray_dir, bData->bNormal[index].invNorm);

    /* a simple hack to minimize chance of ray leaks at identical ray <-> edge locations */
    add_v3_fl(ray_start, 0.001f);

    hit.index = -1;
    hit.dist = BVH_RAYCAST_DIST_MAX;
    nearest.index = -1;
    nearest.dist_sq = brush_radius * brush_radius; /* find_nearest uses squared distance */

    /* Check volume collision */
    if (ELEM(brush->collision, MOD_DPAINT_COL_VOLUME, MOD_DPAINT_COL_VOLDIST)) {
      BLI_bvhtree_ray_cast(
          treeData->tree, ray_start, ray_dir, 0.0f, &hit, mesh_tris_spherecast_dp, treeData);
      if (hit.index != -1) {
        /* We hit a triangle, now check if collision point normal is facing the point */

        /* For optimization sake, hit point normal isn't calculated in ray cast loop */
        const int vtri[3] = {
            mloop[mlooptri[hit.index].tri[0]].v,
            mloop[mlooptri[hit.index].tri[1]].v,
            mloop[mlooptri[hit.index].tri[2]].v,
        };
        float dot;

        normal_tri_v3(hit.no, mvert[vtri[0]].co, mvert[vtri[1]].co, mvert[vtri[2]].co);
        dot = dot_v3v3(ray_dir, hit.no);

        /* If ray and hit face normal are facing same direction
         * hit point is inside a closed mesh. */
        if (dot >= 0.0f) {
          const float dist = hit.dist;
          const int f_index = hit.index;

          /* Also cast a ray in opposite direction to make sure
           * point is at least surrounded by two brush faces */
          negate_v3(ray_dir);
          hit.index = -1;
          hit.dist = BVH_RAYCAST_DIST_MAX;

          BLI_bvhtree_ray_cast(
              treeData->tree, ray_start, ray_dir, 0.0f, &hit, mesh_tris_spherecast_dp, treeData);

          if (hit.index != -1) {
            /* Add factor on supersample filter */
            volume_factor = 1.0f;
            hit_found = HIT_VOLUME;

            /* Mark hit info */

            /* Calculate final hit coordinates */
            madd_v3_v3v3fl(hitCoord, ray_start, ray_dir, hit.dist);

            depth += dist * sample_factor;
            hitTri = f_index;
          }
        }
      }
    }

    /* Check proximity collision */
    if (ELEM(brush->collision, MOD_DPAINT_COL_DIST, MOD_DPAINT_COL_VOLDIST) &&
        (!hit_found || (brush->flags & MOD_DPAINT_INVERSE_PROX))) {
      float proxDist = -1.0f;
      float hitCo[3] = {0.0f, 0.0f, 0.0f};
      int tri = 0;

      /* if inverse prox and no hit found, skip this sample */
      if (inner_proximity && !hit_found) {
        continue;
      }

      /* If pure distance proximity, find the nearest point on the mesh */
      if (!(brush->flags & MOD_DPAINT_PROX_PROJECT)) {
        BLI_bvhtree_find_nearest(
            treeData->tree, ray_start, &nearest, mesh_tris_nearest_point_dp, treeData);
        if (nearest.index != -1) {
          proxDist = sqrtf(nearest.dist_sq);
          copy_v3_v3(hitCo, nearest.co);
          tri = nearest.index;
        }
      }
      else { /* else cast a ray in defined projection direction */
        float proj_ray[3] = {0.0f};

        if (brush->ray_dir == MOD_DPAINT_RAY_CANVAS) {
          copy_v3_v3(proj_ray, bData->bNormal[index].invNorm);
          negate_v3(proj_ray);
        }
        else if (brush->ray_dir == MOD_DPAINT_RAY_BRUSH_AVG) {
          copy_v3_v3(proj_ray, avg_brushNor);
        }
        else { /* MOD_DPAINT_RAY_ZPLUS */
          proj_ray[2] = 1.0f;
        }
        hit.index = -1;
        hit.dist = brush_radius;

        /* Do a face normal directional ray-cast, and use that distance. */
        BLI_bvhtree_ray_cast(
            treeData->tree, ray_start, proj_ray, 0.0f, &hit, mesh_tris_spherecast_dp, treeData);
        if (hit.index != -1) {
          proxDist = hit.dist;

          /* Calculate final hit coordinates */
          madd_v3_v3v3fl(hitCo, ray_start, proj_ray, hit.dist);

          tri = hit.index;
        }
      }

      /* If a hit was found, calculate required values */
      if (proxDist >= 0.0f && proxDist <= brush_radius) {
        proximity_factor = proxDist / brush_radius;
        CLAMP(proximity_factor, 0.0f, 1.0f);
        if (!inner_proximity) {
          proximity_factor = 1.0f - proximity_factor;
        }

        hit_found = HIT_PROXIMITY;

        /* if no volume hit, use prox point face info */
        if (hitTri == -1) {
          copy_v3_v3(hitCoord, hitCo);
          hitTri = tri;
        }
      }
    }

    /* mix final sample strength depending on brush settings */
    if (hit_found) {
      /* If "negate volume" enabled, negate all factors within volume. */
      if (brush->collision == MOD_DPAINT_COL_VOLDIST && brush->flags & MOD_DPAINT_NEGATE_VOLUME) {
        volume_factor = 1.0f - volume_factor;
        if (inner_proximity) {
          proximity_factor = 1.0f - proximity_factor;
        }
      }

      /* apply final sample depending on final hit type */
      if (hit_found == HIT_VOLUME) {
        sampleStrength = volume_factor;
      }
      else if (hit_found == HIT_PROXIMITY) {
        /* apply falloff curve to the proximity_factor */
        if (brush->proximity_falloff == MOD_DPAINT_PRFALL_RAMP &&
            BKE_colorband_evaluate(brush->paint_ramp, (1.0f - proximity_factor), prox_colorband)) {
          proximity_factor = prox_colorband[3];
        }
        else if (brush->proximity_falloff == MOD_DPAINT_PRFALL_CONSTANT) {
          proximity_factor = (!inner_proximity || brush->flags & MOD_DPAINT_NEGATE_VOLUME) ? 1.0f :
                                                                                             0.0f;
        }
        /* apply sample */
        sampleStrength = proximity_factor;
      }

      sampleStrength *= sample_factor;
    }
    else {
      continue;
    }

    /* velocity brush, only do on main sample */
    if (brush->flags & MOD_DPAINT_USES_VELOCITY && ss == 0 && brushVelocity) {
      float weights[3];
      float brushPointVelocity[3];
      float velocity[3];

      const int v1 = mloop[mlooptri[hitTri].tri[0]].v;
      const int v2 = mloop[mlooptri[hitTri].tri[1]].v;
      const int v3 = mloop[mlooptri[hitTri].tri[2]].v;

      /* calculate barycentric weights for hit point */
      interp_weights_tri_v3(weights, mvert[v1].co, mvert[v2].co, mvert[v3].co, hitCoord);

      /* Simple check based on brush surface velocity,
       * TODO: perhaps implement something that handles volume movement as well. */

      /* interpolate vertex speed vectors to get hit point velocity */
      interp_v3_v3v3v3(brushPointVelocity,
                       brushVelocity[v1].v,
                       brushVelocity[v2].v,
                       brushVelocity[v3].v,
                       weights);

      /* subtract canvas point velocity */
      if (bData->velocity) {
        sub_v3_v3v3(velocity, brushPointVelocity, bData->velocity[index].v);
      }
      else {
        copy_v3_v3(velocity, brushPointVelocity);
      }
      velocity_val = normalize_v3(velocity);

      /* if brush has smudge enabled store brush velocity */
      if (surface->type == MOD_DPAINT_SURFACE_T_PAINT && brush->flags & MOD_DPAINT_DO_SMUDGE &&
          bData->brush_velocity) {
        copy_v3_v3(&bData->brush_velocity[index * 4], velocity);
        bData->brush_velocity[index * 4 + 3] = velocity_val;
      }
    }

    /*
     * Process hit color and alpha
     */
    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      float sampleColor[3];
      float alpha_factor = 1.0f;

      sampleColor[0] = brush->r;
      sampleColor[1] = brush->g;
      sampleColor[2] = brush->b;

      /* Sample proximity colorband if required */
      if ((hit_found == HIT_PROXIMITY) && (brush->proximity_falloff == MOD_DPAINT_PRFALL_RAMP)) {
        if (!(brush->flags & MOD_DPAINT_RAMP_ALPHA)) {
          sampleColor[0] = prox_colorband[0];
          sampleColor[1] = prox_colorband[1];
          sampleColor[2] = prox_colorband[2];
        }
      }

      /* Add AA sample */
      paintColor[0] += sampleColor[0];
      paintColor[1] += sampleColor[1];
      paintColor[2] += sampleColor[2];
      sampleStrength *= alpha_factor;
      numOfHits++;
    }

    /* Apply sample strength. */
    brushStrength += sampleStrength;
  } /* End super-sampling. */

  /* If any sample was inside paint range. */
  if (brushStrength > 0.0f || depth > 0.0f) {
    /* Apply super-sampling results. */
    if (samples > 1) {
      brushStrength /= total_sample;
    }
    CLAMP(brushStrength, 0.0f, 1.0f);

    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      /* Get final pixel color and alpha. */
      paintColor[0] /= numOfHits;
      paintColor[1] /= numOfHits;
      paintColor[2] /= numOfHits;
    }
    /* Get final object space depth. */
    else if (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) {
      depth /= bData->bNormal[index].normal_scale * total_sample;
    }

    dynamicPaint_updatePointData(
        surface, index, brush, paintColor, brushStrength, depth, velocity_val, timescale);
  }
}

static bool dynamicPaint_paintMesh(Depsgraph *depsgraph,
                                   DynamicPaintSurface *surface,
                                   DynamicPaintBrushSettings *brush,
                                   Object *brushOb,
                                   Scene *scene,
                                   float timescale)
{
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  Mesh *mesh = NULL;
  Vec3f *brushVelocity = NULL;
  MVert *mvert = NULL;
  const MLoopTri *mlooptri = NULL;
  const MLoop *mloop = NULL;

  if (brush->flags & MOD_DPAINT_USES_VELOCITY) {
    dynamicPaint_brushMeshCalculateVelocity(
        depsgraph, scene, brushOb, brush, &brushVelocity, timescale);
  }

  Mesh *brush_mesh = dynamicPaint_brush_mesh_get(brush);
  if (brush_mesh == NULL) {
    return false;
  }

  {
    BVHTreeFromMesh treeData = {NULL};
    float avg_brushNor[3] = {0.0f};
    const float brush_radius = brush->paint_distance * surface->radius_scale;
    int numOfVerts;
    int ii;
    Bounds3D mesh_bb = {{0}};
    VolumeGrid *grid = bData->grid;

    mesh = BKE_mesh_copy_for_eval(brush_mesh, false);
    mvert = mesh->mvert;
    const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh);
    mlooptri = BKE_mesh_runtime_looptri_ensure(mesh);
    mloop = mesh->mloop;
    numOfVerts = mesh->totvert;

    /* Transform collider vertices to global space
     * (Faster than transforming per surface point
     * coordinates and normals to object space) */
    for (ii = 0; ii < numOfVerts; ii++) {
      mul_m4_v3(brushOb->obmat, mvert[ii].co);
      boundInsert(&mesh_bb, mvert[ii].co);

      /* for proximity project calculate average normal */
      if (brush->flags & MOD_DPAINT_PROX_PROJECT && brush->collision != MOD_DPAINT_COL_VOLUME) {
        float nor[3];
        copy_v3_v3(nor, vert_normals[ii]);
        mul_mat3_m4_v3(brushOb->obmat, nor);
        normalize_v3(nor);

        add_v3_v3(avg_brushNor, nor);
      }
    }

    if (brush->flags & MOD_DPAINT_PROX_PROJECT && brush->collision != MOD_DPAINT_COL_VOLUME) {
      mul_v3_fl(avg_brushNor, 1.0f / (float)numOfVerts);
      /* instead of null vector use positive z */
      if (UNLIKELY(normalize_v3(avg_brushNor) == 0.0f)) {
        avg_brushNor[2] = 1.0f;
      }
    }

    /* check bounding box collision */
    if (grid && meshBrush_boundsIntersect(&grid->grid_bounds, &mesh_bb, brush, brush_radius)) {
      /* Build a bvh tree from transformed vertices */
      if (BKE_bvhtree_from_mesh_get(&treeData, mesh, BVHTREE_FROM_LOOPTRI, 4)) {
        int c_index;
        int total_cells = grid->dim[0] * grid->dim[1] * grid->dim[2];

        /* loop through space partitioning grid */
        for (c_index = 0; c_index < total_cells; c_index++) {
          /* check grid cell bounding box */
          if (!grid->s_num[c_index] ||
              !meshBrush_boundsIntersect(&grid->bounds[c_index], &mesh_bb, brush, brush_radius)) {
            continue;
          }

          /* loop through cell points and process brush */
          DynamicPaintPaintData data = {
              .surface = surface,
              .brush = brush,
              .brushOb = brushOb,
              .scene = scene,
              .timescale = timescale,
              .c_index = c_index,
              .mesh = mesh,
              .mvert = mvert,
              .mloop = mloop,
              .mlooptri = mlooptri,
              .brush_radius = brush_radius,
              .avg_brushNor = avg_brushNor,
              .brushVelocity = brushVelocity,
              .treeData = &treeData,
          };
          TaskParallelSettings settings;
          BLI_parallel_range_settings_defaults(&settings);
          settings.use_threading = (grid->s_num[c_index] > 250);
          BLI_task_parallel_range(0,
                                  grid->s_num[c_index],
                                  &data,
                                  dynamic_paint_paint_mesh_cell_point_cb_ex,
                                  &settings);
        }
      }
    }
    /* free bvh tree */
    free_bvhtree_from_mesh(&treeData);
    BKE_id_free(NULL, mesh);
  }

  /* free brush velocity data */
  if (brushVelocity) {
    MEM_freeN(brushVelocity);
  }

  return true;
}

/*
 * Paint a particle system to the surface
 */
static void dynamic_paint_paint_particle_cell_point_cb_ex(
    void *__restrict userdata, const int id, const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintPaintData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;
  const PaintBakeData *bData = sData->bData;
  VolumeGrid *grid = bData->grid;

  const DynamicPaintBrushSettings *brush = data->brush;

  const ParticleSystem *psys = data->psys;

  const float timescale = data->timescale;
  const int c_index = data->c_index;

  KDTree_3d *tree = data->treeData;

  const float solidradius = data->solidradius;
  const float smooth = brush->particle_smooth * surface->radius_scale;
  const float range = solidradius + smooth;
  const float particle_timestep = 0.04f * psys->part->timetweak;

  const int index = grid->t_index[grid->s_pos[c_index] + id];
  float disp_intersect = 0.0f;
  float radius = 0.0f;
  float strength = 0.0f;
  int part_index = -1;

  /*
   * With predefined radius, there is no variation between particles.
   * It's enough to just find the nearest one.
   */
  {
    KDTreeNearest_3d nearest;
    float smooth_range, part_solidradius;

    /* Find nearest particle and get distance to it */
    BLI_kdtree_3d_find_nearest(tree, bData->realCoord[bData->s_pos[index]].v, &nearest);
    /* if outside maximum range, no other particle can influence either */
    if (nearest.dist > range) {
      return;
    }

    if (brush->flags & MOD_DPAINT_PART_RAD) {
      /* use particles individual size */
      ParticleData *pa = psys->particles + nearest.index;
      part_solidradius = pa->size;
    }
    else {
      part_solidradius = solidradius;
    }
    radius = part_solidradius + smooth;
    if (nearest.dist < radius) {
      /* distances inside solid radius has maximum influence -> dist = 0 */
      smooth_range = max_ff(0.0f, (nearest.dist - part_solidradius));
      /* do smoothness if enabled */
      if (smooth) {
        smooth_range /= smooth;
      }

      strength = 1.0f - smooth_range;
      disp_intersect = radius - nearest.dist;
      part_index = nearest.index;
    }
  }
  /* If using random per particle radius and closest particle didn't give max influence */
  if (brush->flags & MOD_DPAINT_PART_RAD && strength < 1.0f && psys->part->randsize > 0.0f) {
    /*
     * If we use per particle radius, we have to sample all particles
     * within max radius range
     */
    KDTreeNearest_3d *nearest;

    float smooth_range = smooth * (1.0f - strength), dist;
    /* calculate max range that can have particles with higher influence than the nearest one */
    const float max_range = smooth - strength * smooth + solidradius;
    /* Make gcc happy! */
    dist = max_range;

    const int particles = BLI_kdtree_3d_range_search(
        tree, bData->realCoord[bData->s_pos[index]].v, &nearest, max_range);

    /* Find particle that produces highest influence */
    for (int n = 0; n < particles; n++) {
      ParticleData *pa = &psys->particles[nearest[n].index];

      /* skip if out of range */
      if (nearest[n].dist > (pa->size + smooth)) {
        continue;
      }

      /* update hit data */
      const float s_range = nearest[n].dist - pa->size;
      /* skip if higher influence is already found */
      if (smooth_range < s_range) {
        continue;
      }

      /* update hit data */
      smooth_range = s_range;
      dist = nearest[n].dist;
      part_index = nearest[n].index;

      /* If inside solid range and no disp depth required, no need to seek further */
      if ((s_range < 0.0f) &&
          !ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) {
        break;
      }
    }

    if (nearest) {
      MEM_freeN(nearest);
    }

    /* now calculate influence for this particle */
    const float rad = radius + smooth;
    if ((rad - dist) > disp_intersect) {
      disp_intersect = radius - dist;
      radius = rad;
    }

    /* do smoothness if enabled */
    CLAMP_MIN(smooth_range, 0.0f);
    if (smooth) {
      smooth_range /= smooth;
    }

    const float str = 1.0f - smooth_range;
    /* if influence is greater, use this one */
    if (str > strength) {
      strength = str;
    }
  }

  if (strength > 0.001f) {
    float paintColor[4] = {0.0f};
    float depth = 0.0f;
    float velocity_val = 0.0f;

    /* apply velocity */
    if ((brush->flags & MOD_DPAINT_USES_VELOCITY) && (part_index != -1)) {
      float velocity[3];
      ParticleData *pa = psys->particles + part_index;
      mul_v3_v3fl(velocity, pa->state.vel, particle_timestep);

      /* subtract canvas point velocity */
      if (bData->velocity) {
        sub_v3_v3(velocity, bData->velocity[index].v);
      }
      velocity_val = normalize_v3(velocity);

      /* store brush velocity for smudge */
      if ((surface->type == MOD_DPAINT_SURFACE_T_PAINT) &&
          (brush->flags & MOD_DPAINT_DO_SMUDGE && bData->brush_velocity)) {
        copy_v3_v3(&bData->brush_velocity[index * 4], velocity);
        bData->brush_velocity[index * 4 + 3] = velocity_val;
      }
    }

    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      copy_v3_v3(paintColor, &brush->r);
    }
    else if (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) {
      /* get displace depth */
      disp_intersect = (1.0f - sqrtf(disp_intersect / radius)) * radius;
      depth = max_ff(0.0f, (radius - disp_intersect) / bData->bNormal[index].normal_scale);
    }

    dynamicPaint_updatePointData(
        surface, index, brush, paintColor, strength, depth, velocity_val, timescale);
  }
}

static bool dynamicPaint_paintParticles(DynamicPaintSurface *surface,
                                        ParticleSystem *psys,
                                        DynamicPaintBrushSettings *brush,
                                        float timescale)
{
  ParticleSettings *part = psys->part;
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  VolumeGrid *grid = bData->grid;

  KDTree_3d *tree;
  int particlesAdded = 0;
  int invalidParticles = 0;
  int p = 0;

  const float solidradius = surface->radius_scale * ((brush->flags & MOD_DPAINT_PART_RAD) ?
                                                         part->size :
                                                         brush->particle_radius);
  const float smooth = brush->particle_smooth * surface->radius_scale;

  const float range = solidradius + smooth;

  Bounds3D part_bb = {{0}};

  if (psys->totpart < 1) {
    return true;
  }

  /*
   * Build a KD-tree to optimize distance search
   */
  tree = BLI_kdtree_3d_new(psys->totpart);

  /* loop through particles and insert valid ones to the tree */
  p = 0;
  for (ParticleData *pa = psys->particles; p < psys->totpart; p++, pa++) {
    /* Proceed only if particle is active */
    if ((pa->alive == PARS_UNBORN && (part->flag & PART_UNBORN) == 0) ||
        (pa->alive == PARS_DEAD && (part->flag & PART_DIED) == 0) || (pa->flag & PARS_UNEXIST)) {
      continue;
    }

    /* for debug purposes check if any NAN particle proceeds
     * For some reason they get past activity check, this should rule most of them out */
    if (isnan(pa->state.co[0]) || isnan(pa->state.co[1]) || isnan(pa->state.co[2])) {
      invalidParticles++;
      continue;
    }

    /* make sure particle is close enough to canvas */
    if (!boundIntersectPoint(&grid->grid_bounds, pa->state.co, range)) {
      continue;
    }

    BLI_kdtree_3d_insert(tree, p, pa->state.co);

    /* calc particle system bounds */
    boundInsert(&part_bb, pa->state.co);

    particlesAdded++;
  }
  if (invalidParticles) {
    CLOG_WARN(&LOG, "Invalid particle(s) found!");
  }

  /* If no suitable particles were found, exit */
  if (particlesAdded < 1) {
    BLI_kdtree_3d_free(tree);
    return true;
  }

  /* only continue if particle bb is close enough to canvas bb */
  if (boundsIntersectDist(&grid->grid_bounds, &part_bb, range)) {
    int c_index;
    int total_cells = grid->dim[0] * grid->dim[1] * grid->dim[2];

    /* balance tree */
    BLI_kdtree_3d_balance(tree);

    /* loop through space partitioning grid */
    for (c_index = 0; c_index < total_cells; c_index++) {
      /* check cell bounding box */
      if (!grid->s_num[c_index] || !boundsIntersectDist(&grid->bounds[c_index], &part_bb, range)) {
        continue;
      }

      /* loop through cell points */
      DynamicPaintPaintData data = {
          .surface = surface,
          .brush = brush,
          .psys = psys,
          .solidradius = solidradius,
          .timescale = timescale,
          .c_index = c_index,
          .treeData = tree,
      };
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (grid->s_num[c_index] > 250);
      BLI_task_parallel_range(0,
                              grid->s_num[c_index],
                              &data,
                              dynamic_paint_paint_particle_cell_point_cb_ex,
                              &settings);
    }
  }
  BLI_kdtree_3d_free(tree);

  return true;
}

/* paint a single point of defined proximity radius to the surface */
static void dynamic_paint_paint_single_point_cb_ex(void *__restrict userdata,
                                                   const int index,
                                                   const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintPaintData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;
  const PaintBakeData *bData = sData->bData;

  const DynamicPaintBrushSettings *brush = data->brush;

  const float timescale = data->timescale;

  const float brush_radius = data->brush_radius;
  const Vec3f *brushVelocity = data->brushVelocity;

  float *pointCoord = data->pointCoord;

  const float distance = len_v3v3(pointCoord, bData->realCoord[bData->s_pos[index]].v);
  float colorband[4] = {0.0f};
  float strength;

  if (distance > brush_radius) {
    return;
  }

  /* Smooth range or color ramp */
  if (ELEM(brush->proximity_falloff, MOD_DPAINT_PRFALL_SMOOTH, MOD_DPAINT_PRFALL_RAMP)) {
    strength = 1.0f - distance / brush_radius;
    CLAMP(strength, 0.0f, 1.0f);
  }
  else {
    strength = 1.0f;
  }

  if (strength >= 0.001f) {
    float paintColor[3] = {0.0f};
    float depth = 0.0f;
    float velocity_val = 0.0f;

    /* color ramp */
    if (brush->proximity_falloff == MOD_DPAINT_PRFALL_RAMP &&
        BKE_colorband_evaluate(brush->paint_ramp, (1.0f - strength), colorband)) {
      strength = colorband[3];
    }

    if (brush->flags & MOD_DPAINT_USES_VELOCITY) {
      float velocity[3];

      /* subtract canvas point velocity */
      if (bData->velocity) {
        sub_v3_v3v3(velocity, brushVelocity->v, bData->velocity[index].v);
      }
      else {
        copy_v3_v3(velocity, brushVelocity->v);
      }
      velocity_val = len_v3(velocity);

      /* store brush velocity for smudge */
      if (surface->type == MOD_DPAINT_SURFACE_T_PAINT && brush->flags & MOD_DPAINT_DO_SMUDGE &&
          bData->brush_velocity) {
        mul_v3_v3fl(&bData->brush_velocity[index * 4], velocity, 1.0f / velocity_val);
        bData->brush_velocity[index * 4 + 3] = velocity_val;
      }
    }

    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      if (brush->proximity_falloff == MOD_DPAINT_PRFALL_RAMP &&
          !(brush->flags & MOD_DPAINT_RAMP_ALPHA)) {
        paintColor[0] = colorband[0];
        paintColor[1] = colorband[1];
        paintColor[2] = colorband[2];
      }
      else {
        paintColor[0] = brush->r;
        paintColor[1] = brush->g;
        paintColor[2] = brush->b;
      }
    }
    else if (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) {
      /* get displace depth */
      const float disp_intersect = (1.0f - sqrtf((brush_radius - distance) / brush_radius)) *
                                   brush_radius;
      depth = max_ff(0.0f, (brush_radius - disp_intersect) / bData->bNormal[index].normal_scale);
    }
    dynamicPaint_updatePointData(
        surface, index, brush, paintColor, strength, depth, velocity_val, timescale);
  }
}

static bool dynamicPaint_paintSinglePoint(
    Depsgraph *depsgraph,
    DynamicPaintSurface *surface,
    /* Cannot be const, because it is assigned to non-const variable.
     * NOLINTNEXTLINE: readability-non-const-parameter. */
    float *pointCoord,
    DynamicPaintBrushSettings *brush,
    Object *brushOb,
    Scene *scene,
    float timescale)
{
  PaintSurfaceData *sData = surface->data;
  float brush_radius = brush->paint_distance * surface->radius_scale;
  Vec3f brushVel;

  if (brush->flags & MOD_DPAINT_USES_VELOCITY) {
    dynamicPaint_brushObjectCalculateVelocity(depsgraph, scene, brushOb, &brushVel, timescale);
  }

  const Mesh *brush_mesh = dynamicPaint_brush_mesh_get(brush);
  const MVert *mvert = brush_mesh->mvert;

  /*
   * Loop through every surface point
   */
  DynamicPaintPaintData data = {
      .surface = surface,
      .brush = brush,
      .brushOb = brushOb,
      .scene = scene,
      .timescale = timescale,
      .mvert = mvert,
      .brush_radius = brush_radius,
      .brushVelocity = &brushVel,
      .pointCoord = pointCoord,
  };
  TaskParallelSettings settings;
  BLI_parallel_range_settings_defaults(&settings);
  settings.use_threading = (sData->total_points > 1000);
  BLI_task_parallel_range(
      0, sData->total_points, &data, dynamic_paint_paint_single_point_cb_ex, &settings);

  return true;
}

/***************************** Dynamic Paint Step / Baking ******************************/

/*
 * Calculate current frame distances and directions for adjacency data
 */

static void dynamic_paint_prepare_adjacency_cb(void *__restrict userdata,
                                               const int index,
                                               const TaskParallelTLS *__restrict UNUSED(tls))
{
  PaintSurfaceData *sData = userdata;
  PaintBakeData *bData = sData->bData;
  BakeAdjPoint *bNeighs = bData->bNeighs;
  PaintAdjData *adj_data = sData->adj_data;
  Vec3f *realCoord = bData->realCoord;

  const int num_neighs = adj_data->n_num[index];

  for (int i = 0; i < num_neighs; i++) {
    const int n_index = adj_data->n_index[index] + i;
    const int t_index = adj_data->n_target[n_index];

    /* dir vec */
    sub_v3_v3v3(bNeighs[n_index].dir,
                realCoord[bData->s_pos[t_index]].v,
                realCoord[bData->s_pos[index]].v);
    /* dist */
    bNeighs[n_index].dist = normalize_v3(bNeighs[n_index].dir);
  }
}

static void dynamicPaint_prepareAdjacencyData(DynamicPaintSurface *surface, const bool force_init)
{
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  BakeAdjPoint *bNeighs;
  PaintAdjData *adj_data = sData->adj_data;

  int index;

  if ((!surface_usesAdjDistance(surface) && !force_init) || !sData->adj_data) {
    return;
  }

  if (bData->bNeighs) {
    MEM_freeN(bData->bNeighs);
  }
  bNeighs = bData->bNeighs = MEM_mallocN(sData->adj_data->total_targets * sizeof(*bNeighs),
                                         "PaintEffectBake");
  if (!bNeighs) {
    return;
  }

  TaskParallelSettings settings;
  BLI_parallel_range_settings_defaults(&settings);
  settings.use_threading = (sData->total_points > 1000);
  BLI_task_parallel_range(
      0, sData->total_points, sData, dynamic_paint_prepare_adjacency_cb, &settings);

  /* calculate average values (single thread).
   * NOTE: tried to put this in threaded callback (using _reduce feature),
   * but gave ~30% slower result! */
  bData->average_dist = 0.0;
  for (index = 0; index < sData->total_points; index++) {
    int numOfNeighs = adj_data->n_num[index];

    for (int i = 0; i < numOfNeighs; i++) {
      bData->average_dist += (double)bNeighs[adj_data->n_index[index] + i].dist;
    }
  }
  bData->average_dist /= adj_data->total_targets;
}

/* Find two adjacency points (closest_id) and influence (closest_d)
 * to move paint towards when affected by a force. */
static void surface_determineForceTargetPoints(const PaintSurfaceData *sData,
                                               const int index,
                                               const float force[3],
                                               float closest_d[2],
                                               int closest_id[2])
{
  BakeAdjPoint *bNeighs = sData->bData->bNeighs;
  const int numOfNeighs = sData->adj_data->n_num[index];

  closest_id[0] = closest_id[1] = -1;
  closest_d[0] = closest_d[1] = -1.0f;

  /* find closest neigh */
  for (int i = 0; i < numOfNeighs; i++) {
    const int n_index = sData->adj_data->n_index[index] + i;
    const float dir_dot = dot_v3v3(bNeighs[n_index].dir, force);

    if (dir_dot > closest_d[0] && dir_dot > 0.0f) {
      closest_d[0] = dir_dot;
      closest_id[0] = n_index;
    }
  }

  if (closest_d[0] < 0.0f) {
    return;
  }

  /* find second closest neigh */
  for (int i = 0; i < numOfNeighs; i++) {
    const int n_index = sData->adj_data->n_index[index] + i;

    if (n_index == closest_id[0]) {
      continue;
    }

    const float dir_dot = dot_v3v3(bNeighs[n_index].dir, force);
    const float closest_dot = dot_v3v3(bNeighs[n_index].dir, bNeighs[closest_id[0]].dir);

    /* only accept neighbor at "other side" of the first one in relation to force dir
     * so make sure angle between this and closest neigh is greater than first angle. */
    if (dir_dot > closest_d[1] && closest_dot < closest_d[0] && dir_dot > 0.0f) {
      closest_d[1] = dir_dot;
      closest_id[1] = n_index;
    }
  }

  /* if two valid neighs found, calculate how force effect is divided evenly between them
   * (so that d[0] + d[1] = 1.0) */
  if (closest_id[1] != -1) {
    float force_proj[3];
    float tangent[3];
    const float neigh_diff = acosf(
        dot_v3v3(bNeighs[closest_id[0]].dir, bNeighs[closest_id[1]].dir));
    float force_intersect;
    float temp;

    /* project force vector on the plane determined by these two neighbor points
     * and calculate relative force angle from it. */
    cross_v3_v3v3(tangent, bNeighs[closest_id[0]].dir, bNeighs[closest_id[1]].dir);
    normalize_v3(tangent);
    force_intersect = dot_v3v3(force, tangent);
    madd_v3_v3v3fl(force_proj, force, tangent, (-1.0f) * force_intersect);
    normalize_v3(force_proj);

    /* get drip factor based on force dir in relation to angle between those neighbors */
    temp = dot_v3v3(bNeighs[closest_id[0]].dir, force_proj);
    CLAMP(temp, -1.0f, 1.0f); /* float precision might cause values > 1.0f that return infinite */
    closest_d[1] = acosf(temp) / neigh_diff;
    closest_d[0] = 1.0f - closest_d[1];

    /* and multiply depending on how deeply force intersects surface */
    temp = fabsf(force_intersect);
    CLAMP(temp, 0.0f, 1.0f);
    mul_v2_fl(closest_d, acosf(temp) / (float)M_PI_2);
  }
  else {
    /* if only single neighbor, still linearize force intersection effect */
    closest_d[0] = 1.0f - acosf(closest_d[0]) / (float)M_PI_2;
  }
}

static void dynamicPaint_doSmudge(DynamicPaintSurface *surface,
                                  DynamicPaintBrushSettings *brush,
                                  float timescale)
{
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  BakeAdjPoint *bNeighs = sData->bData->bNeighs;
  float max_velocity = 0.0f;

  if (!sData->adj_data) {
    return;
  }

  /* find max velocity */
  for (int index = 0; index < sData->total_points; index++) {
    float vel = bData->brush_velocity[index * 4 + 3];
    CLAMP_MIN(max_velocity, vel);
  }

  int steps = (int)ceil((double)max_velocity / bData->average_dist * (double)timescale);
  CLAMP(steps, 0, 12);
  float eff_scale = brush->smudge_strength / (float)steps * timescale;

  for (int step = 0; step < steps; step++) {
    for (int index = 0; index < sData->total_points; index++) {

      if (sData->adj_data->flags[index] & ADJ_BORDER_PIXEL) {
        continue;
      }

      PaintPoint *pPoint = &((PaintPoint *)sData->type_data)[index];
      float smudge_str = bData->brush_velocity[index * 4 + 3];

      /* force targets */
      int closest_id[2];
      float closest_d[2];

      if (!smudge_str) {
        continue;
      }

      /* get force affect points */
      surface_determineForceTargetPoints(
          sData, index, &bData->brush_velocity[index * 4], closest_d, closest_id);

      /* Apply movement towards those two points */
      for (int i = 0; i < 2; i++) {
        int n_index = closest_id[i];
        if (n_index != -1 && closest_d[i] > 0.0f) {
          float dir_dot = closest_d[i], dir_factor;
          float speed_scale = eff_scale * smudge_str / bNeighs[n_index].dist;
          PaintPoint *ePoint = &(
              (PaintPoint *)sData->type_data)[sData->adj_data->n_target[n_index]];

          /* just skip if angle is too extreme */
          if (dir_dot <= 0.0f) {
            continue;
          }

          dir_factor = dir_dot * speed_scale;
          CLAMP_MAX(dir_factor, brush->smudge_strength);

          /* mix new color and alpha */
          mixColors(ePoint->color, ePoint->color[3], pPoint->color, pPoint->color[3], dir_factor);
          ePoint->color[3] = ePoint->color[3] * (1.0f - dir_factor) +
                             pPoint->color[3] * dir_factor;

          /* smudge "wet layer" */
          mixColors(ePoint->e_color,
                    ePoint->e_color[3],
                    pPoint->e_color,
                    pPoint->e_color[3],
                    dir_factor);
          ePoint->e_color[3] = ePoint->e_color[3] * (1.0f - dir_factor) +
                               pPoint->e_color[3] * dir_factor;
          pPoint->wetness *= (1.0f - dir_factor);
        }
      }
    }
  }
}

typedef struct DynamicPaintEffectData {
  const DynamicPaintSurface *surface;
  Scene *scene;

  float *force;
  ListBase *effectors;
  const void *prevPoint;
  const float eff_scale;

  uint8_t *point_locks;

  const float wave_speed;
  const float wave_scale;
  const float wave_max_slope;

  const float dt;
  const float min_dist;
  const float damp_factor;
  const bool reset_wave;
} DynamicPaintEffectData;

/*
 * Prepare data required by effects for current frame.
 * Returns number of steps required
 */
static void dynamic_paint_prepare_effect_cb(void *__restrict userdata,
                                            const int index,
                                            const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintEffectData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;
  const PaintBakeData *bData = sData->bData;
  Vec3f *realCoord = bData->realCoord;

  Scene *scene = data->scene;

  float *force = data->force;
  ListBase *effectors = data->effectors;

  float forc[3] = {0};
  float vel[3] = {0};

  /* apply force fields */
  if (effectors) {
    EffectedPoint epoint;
    pd_point_from_loc(scene, realCoord[bData->s_pos[index]].v, vel, index, &epoint);
    epoint.vel_to_sec = 1.0f;
    BKE_effectors_apply(effectors, NULL, surface->effector_weights, &epoint, forc, NULL, NULL);
  }

  /* if global gravity is enabled, add it too */
  if (scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY) {
    /* also divide by 10 to about match default grav
     * with default force strength (1.0). */
    madd_v3_v3fl(forc,
                 scene->physics_settings.gravity,
                 surface->effector_weights->global_gravity * surface->effector_weights->weight[0] /
                     10.0f);
  }

  /* add surface point velocity and acceleration if enabled */
  if (bData->velocity) {
    if (surface->drip_vel) {
      madd_v3_v3fl(forc, bData->velocity[index].v, surface->drip_vel * (-1.0f));
    }

    /* acceleration */
    if (bData->prev_velocity && surface->drip_acc) {
      float acc[3];
      copy_v3_v3(acc, bData->velocity[index].v);
      sub_v3_v3(acc, bData->prev_velocity[index].v);
      madd_v3_v3fl(forc, acc, surface->drip_acc * (-1.0f));
    }
  }

  /* force strength, and normalize force vec */
  force[index * 4 + 3] = normalize_v3_v3(&force[index * 4], forc);
}

static int dynamicPaint_prepareEffectStep(struct Depsgraph *depsgraph,
                                          DynamicPaintSurface *surface,
                                          Scene *scene,
                                          Object *ob,
                                          float **force,
                                          float timescale)
{
  double average_force = 0.0f;
  float shrink_speed = 0.0f, spread_speed = 0.0f;
  float fastest_effect, avg_dist;
  int steps;
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;

  /* Init force data if required */
  if (surface->effect & MOD_DPAINT_EFFECT_DO_DRIP) {
    ListBase *effectors = BKE_effectors_create(
        depsgraph, ob, NULL, surface->effector_weights, false);

    /* allocate memory for force data (dir vector + strength) */
    *force = MEM_mallocN(sizeof(float[4]) * sData->total_points, "PaintEffectForces");

    if (*force) {
      DynamicPaintEffectData data = {
          .surface = surface,
          .scene = scene,
          .force = *force,
          .effectors = effectors,
      };
      TaskParallelSettings settings;
      BLI_parallel_range_settings_defaults(&settings);
      settings.use_threading = (sData->total_points > 1000);
      BLI_task_parallel_range(
          0, sData->total_points, &data, dynamic_paint_prepare_effect_cb, &settings);

      /* calculate average values (single thread) */
      for (int index = 0; index < sData->total_points; index++) {
        average_force += (double)(*force)[index * 4 + 3];
      }
      average_force /= sData->total_points;
    }
    BKE_effectors_free(effectors);
  }

  /* Get number of required steps using average point distance
   * so that just a few ultra close pixels won't increase substeps to max. */

  /* adjust number of required substep by fastest active effect */
  if (surface->effect & MOD_DPAINT_EFFECT_DO_SPREAD) {
    spread_speed = surface->spread_speed;
  }
  if (surface->effect & MOD_DPAINT_EFFECT_DO_SHRINK) {
    shrink_speed = surface->shrink_speed;
  }

  fastest_effect = max_fff(spread_speed, shrink_speed, average_force);
  avg_dist = bData->average_dist * (double)CANVAS_REL_SIZE / (double)getSurfaceDimension(sData);

  steps = (int)ceilf(1.5f * EFF_MOVEMENT_PER_FRAME * fastest_effect / avg_dist * timescale);
  CLAMP(steps, 1, 20);

  return steps;
}

/**
 * Processes active effect step.
 */
static void dynamic_paint_effect_spread_cb(void *__restrict userdata,
                                           const int index,
                                           const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintEffectData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;

  if (sData->adj_data->flags[index] & ADJ_BORDER_PIXEL) {
    return;
  }

  const int numOfNeighs = sData->adj_data->n_num[index];
  BakeAdjPoint *bNeighs = sData->bData->bNeighs;
  PaintPoint *pPoint = &((PaintPoint *)sData->type_data)[index];
  const PaintPoint *prevPoint = data->prevPoint;
  const float eff_scale = data->eff_scale;

  const int *n_index = sData->adj_data->n_index;
  const int *n_target = sData->adj_data->n_target;

  /* Loop through neighboring points */
  for (int i = 0; i < numOfNeighs; i++) {
    const int n_idx = n_index[index] + i;
    float w_factor;
    const PaintPoint *pPoint_prev = &prevPoint[n_target[n_idx]];
    const float speed_scale = (bNeighs[n_idx].dist < eff_scale) ? 1.0f :
                                                                  eff_scale / bNeighs[n_idx].dist;
    const float color_mix = min_fff(pPoint_prev->wetness, pPoint->wetness, 1.0f) * 0.25f *
                            surface->color_spread_speed;

    /* do color mixing */
    if (color_mix) {
      mixColors(pPoint->e_color,
                pPoint->e_color[3],
                pPoint_prev->e_color,
                pPoint_prev->e_color[3],
                color_mix);
    }

    /* Only continue if surrounding point has higher wetness */
    if (pPoint_prev->wetness < pPoint->wetness || pPoint_prev->wetness < MIN_WETNESS) {
      continue;
    }

    w_factor = 1.0f / numOfNeighs * min_ff(pPoint_prev->wetness, 1.0f) * speed_scale;
    CLAMP(w_factor, 0.0f, 1.0f);

    /* mix new wetness and color */
    pPoint->wetness = pPoint->wetness + w_factor * (pPoint_prev->wetness - pPoint->wetness);
    pPoint->e_color[3] = mixColors(pPoint->e_color,
                                   pPoint->e_color[3],
                                   pPoint_prev->e_color,
                                   pPoint_prev->e_color[3],
                                   w_factor);
  }
}

static void dynamic_paint_effect_shrink_cb(void *__restrict userdata,
                                           const int index,
                                           const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintEffectData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;

  if (sData->adj_data->flags[index] & ADJ_BORDER_PIXEL) {
    return;
  }

  const int numOfNeighs = sData->adj_data->n_num[index];
  BakeAdjPoint *bNeighs = sData->bData->bNeighs;
  PaintPoint *pPoint = &((PaintPoint *)sData->type_data)[index];
  const PaintPoint *prevPoint = data->prevPoint;
  const float eff_scale = data->eff_scale;
  float totalAlpha = 0.0f;

  const int *n_index = sData->adj_data->n_index;
  const int *n_target = sData->adj_data->n_target;

  /* Loop through neighboring points */
  for (int i = 0; i < numOfNeighs; i++) {
    const int n_idx = n_index[index] + i;
    const float speed_scale = (bNeighs[n_idx].dist < eff_scale) ? 1.0f :
                                                                  eff_scale / bNeighs[n_idx].dist;
    const PaintPoint *pPoint_prev = &prevPoint[n_target[n_idx]];
    float a_factor, ea_factor, w_factor;

    totalAlpha += pPoint_prev->e_color[3];

    /* Check if neighboring point has lower alpha,
     * if so, decrease this point's alpha as well. */
    if (pPoint->color[3] <= 0.0f && pPoint->e_color[3] <= 0.0f && pPoint->wetness <= 0.0f) {
      continue;
    }

    /* decrease factor for dry paint alpha */
    a_factor = max_ff((1.0f - pPoint_prev->color[3]) / numOfNeighs *
                          (pPoint->color[3] - pPoint_prev->color[3]) * speed_scale,
                      0.0f);
    /* decrease factor for wet paint alpha */
    ea_factor = max_ff((1.0f - pPoint_prev->e_color[3]) / 8 *
                           (pPoint->e_color[3] - pPoint_prev->e_color[3]) * speed_scale,
                       0.0f);
    /* decrease factor for paint wetness */
    w_factor = max_ff((1.0f - pPoint_prev->wetness) / 8 *
                          (pPoint->wetness - pPoint_prev->wetness) * speed_scale,
                      0.0f);

    pPoint->color[3] -= a_factor;
    CLAMP_MIN(pPoint->color[3], 0.0f);
    pPoint->e_color[3] -= ea_factor;
    CLAMP_MIN(pPoint->e_color[3], 0.0f);
    pPoint->wetness -= w_factor;
    CLAMP_MIN(pPoint->wetness, 0.0f);
  }
}

static void dynamic_paint_effect_drip_cb(void *__restrict userdata,
                                         const int index,
                                         const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintEffectData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;

  if (sData->adj_data->flags[index] & ADJ_BORDER_PIXEL) {
    return;
  }

  BakeAdjPoint *bNeighs = sData->bData->bNeighs;
  PaintPoint *pPoint = &((PaintPoint *)sData->type_data)[index];
  const PaintPoint *prevPoint = data->prevPoint;
  const PaintPoint *pPoint_prev = &prevPoint[index];
  const float *force = data->force;
  const float eff_scale = data->eff_scale;

  const int *n_target = sData->adj_data->n_target;

  uint8_t *point_locks = data->point_locks;

  int closest_id[2];
  float closest_d[2];

  /* adjust drip speed depending on wetness */
  float w_factor = pPoint_prev->wetness - 0.025f;
  if (w_factor <= 0) {
    return;
  }
  CLAMP(w_factor, 0.0f, 1.0f);

  float ppoint_wetness_diff = 0.0f;

  /* get force affect points */
  surface_determineForceTargetPoints(sData, index, &force[index * 4], closest_d, closest_id);

  /* Apply movement towards those two points */
  for (int i = 0; i < 2; i++) {
    const int n_idx = closest_id[i];
    if (n_idx != -1 && closest_d[i] > 0.0f) {
      const float dir_dot = closest_d[i];

      /* just skip if angle is too extreme */
      if (dir_dot <= 0.0f) {
        continue;
      }

      float dir_factor, a_factor;
      const float speed_scale = eff_scale * force[index * 4 + 3] / bNeighs[n_idx].dist;

      const unsigned int n_trgt = (unsigned int)n_target[n_idx];

      /* Sort of spinlock, but only for given ePoint.
       * Since the odds a same ePoint is modified at the same time by several threads is very low,
       * this is much more efficient than a global spin lock. */
      const unsigned int epointlock_idx = n_trgt / 8;
      const uint8_t epointlock_bitmask = 1 << (n_trgt & 7); /* 7 == 0b111 */
      while (atomic_fetch_and_or_uint8(&point_locks[epointlock_idx], epointlock_bitmask) &
             epointlock_bitmask) {
        /* pass */
      }

      PaintPoint *ePoint = &((PaintPoint *)sData->type_data)[n_trgt];
      const float e_wet = ePoint->wetness;

      dir_factor = min_ff(0.5f, dir_dot * min_ff(speed_scale, 1.0f) * w_factor);

      /* mix new wetness */
      ePoint->wetness += dir_factor;
      CLAMP(ePoint->wetness, 0.0f, MAX_WETNESS);

      /* mix new color */
      a_factor = dir_factor / pPoint_prev->wetness;
      CLAMP(a_factor, 0.0f, 1.0f);
      mixColors(ePoint->e_color,
                ePoint->e_color[3],
                pPoint_prev->e_color,
                pPoint_prev->e_color[3],
                a_factor);
      /* dripping is supposed to preserve alpha level */
      if (pPoint_prev->e_color[3] > ePoint->e_color[3]) {
        ePoint->e_color[3] += a_factor * pPoint_prev->e_color[3];
        CLAMP_MAX(ePoint->e_color[3], pPoint_prev->e_color[3]);
      }

      /* Decrease paint wetness on current point (just store diff here,
       * that way we can only lock current point once at the end to apply it). */
      ppoint_wetness_diff += (ePoint->wetness - e_wet);

#ifndef NDEBUG
      {
        uint8_t ret = atomic_fetch_and_and_uint8(&point_locks[epointlock_idx],
                                                 ~epointlock_bitmask);
        BLI_assert(ret & epointlock_bitmask);
      }
#else
      atomic_fetch_and_and_uint8(&point_locks[epointlock_idx], ~epointlock_bitmask);
#endif
    }
  }

  {
    const unsigned int ppointlock_idx = index / 8;
    const uint8_t ppointlock_bitmask = 1 << (index & 7); /* 7 == 0b111 */
    while (atomic_fetch_and_or_uint8(&point_locks[ppointlock_idx], ppointlock_bitmask) &
           ppointlock_bitmask) {
      /* pass */
    }

    pPoint->wetness -= ppoint_wetness_diff;
    CLAMP(pPoint->wetness, 0.0f, MAX_WETNESS);

#ifndef NDEBUG
    {
      uint8_t ret = atomic_fetch_and_and_uint8(&point_locks[ppointlock_idx], ~ppointlock_bitmask);
      BLI_assert(ret & ppointlock_bitmask);
    }
#else
    atomic_fetch_and_and_uint8(&point_locks[ppointlock_idx], ~ppointlock_bitmask);
#endif
  }
}

static void dynamicPaint_doEffectStep(
    DynamicPaintSurface *surface,
    /* Cannot be const, because it is assigned to non-const variable.
     * NOLINTNEXTLINE: readability-non-const-parameter. */
    float *force,
    PaintPoint *prevPoint,
    float timescale,
    float steps)
{
  PaintSurfaceData *sData = surface->data;

  const float distance_scale = getSurfaceDimension(sData) / CANVAS_REL_SIZE;
  timescale /= steps;

  if (!sData->adj_data) {
    return;
  }

  /*
   * Spread Effect
   */
  if (surface->effect & MOD_DPAINT_EFFECT_DO_SPREAD) {
    const float eff_scale = distance_scale * EFF_MOVEMENT_PER_FRAME * surface->spread_speed *
                            timescale;

    /* Copy current surface to the previous points array to read unmodified values */
    memcpy(prevPoint, sData->type_data, sData->total_points * sizeof(struct PaintPoint));

    DynamicPaintEffectData data = {
        .surface = surface,
        .prevPoint = prevPoint,
        .eff_scale = eff_scale,
    };
    TaskParallelSettings settings;
    BLI_parallel_range_settings_defaults(&settings);
    settings.use_threading = (sData->total_points > 1000);
    BLI_task_parallel_range(
        0, sData->total_points, &data, dynamic_paint_effect_spread_cb, &settings);
  }

  /*
   * Shrink Effect
   */
  if (surface->effect & MOD_DPAINT_EFFECT_DO_SHRINK) {
    const float eff_scale = distance_scale * EFF_MOVEMENT_PER_FRAME * surface->shrink_speed *
                            timescale;

    /* Copy current surface to the previous points array to read unmodified values */
    memcpy(prevPoint, sData->type_data, sData->total_points * sizeof(struct PaintPoint));

    DynamicPaintEffectData data = {
        .surface = surface,
        .prevPoint = prevPoint,
        .eff_scale = eff_scale,
    };
    TaskParallelSettings settings;
    BLI_parallel_range_settings_defaults(&settings);
    settings.use_threading = (sData->total_points > 1000);
    BLI_task_parallel_range(
        0, sData->total_points, &data, dynamic_paint_effect_shrink_cb, &settings);
  }

  /*
   * Drip Effect
   */
  if (surface->effect & MOD_DPAINT_EFFECT_DO_DRIP && force) {
    const float eff_scale = distance_scale * EFF_MOVEMENT_PER_FRAME * timescale / 2.0f;

    /* Same as #BLI_bitmask, but handled atomically as 'ePoint' locks. */
    const size_t point_locks_size = (sData->total_points / 8) + 1;
    uint8_t *point_locks = MEM_callocN(sizeof(*point_locks) * point_locks_size, __func__);

    /* Copy current surface to the previous points array to read unmodified values */
    memcpy(prevPoint, sData->type_data, sData->total_points * sizeof(struct PaintPoint));

    DynamicPaintEffectData data = {
        .surface = surface,
        .prevPoint = prevPoint,
        .eff_scale = eff_scale,
        .force = force,
        .point_locks = point_locks,
    };
    TaskParallelSettings settings;
    BLI_parallel_range_settings_defaults(&settings);
    settings.use_threading = (sData->total_points > 1000);
    BLI_task_parallel_range(
        0, sData->total_points, &data, dynamic_paint_effect_drip_cb, &settings);

    MEM_freeN(point_locks);
  }
}

static void dynamic_paint_border_cb(void *__restrict userdata,
                                    const int b_index,
                                    const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintEffectData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;

  const int index = sData->adj_data->border[b_index];

  const int numOfNeighs = sData->adj_data->n_num[index];
  PaintPoint *pPoint = &((PaintPoint *)sData->type_data)[index];

  const int *n_index = sData->adj_data->n_index;
  const int *n_target = sData->adj_data->n_target;

  /* Average neighboring points. Intermediaries use premultiplied alpha. */
  float mix_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
  float mix_e_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
  float mix_wetness = 0.0f;

  for (int i = 0; i < numOfNeighs; i++) {
    const int n_idx = n_index[index] + i;
    const int target = n_target[n_idx];

    PaintPoint *pPoint2 = &((PaintPoint *)sData->type_data)[target];

    BLI_assert(!(sData->adj_data->flags[target] & ADJ_BORDER_PIXEL));

    madd_v3_v3fl(mix_color, pPoint2->color, pPoint2->color[3]);
    mix_color[3] += pPoint2->color[3];

    madd_v3_v3fl(mix_e_color, pPoint2->e_color, pPoint2->e_color[3]);
    mix_e_color[3] += pPoint2->e_color[3];

    mix_wetness += pPoint2->wetness;
  }

  const float divisor = 1.0f / numOfNeighs;

  if (mix_color[3]) {
    pPoint->color[3] = mix_color[3] * divisor;
    mul_v3_v3fl(pPoint->color, mix_color, divisor / pPoint->color[3]);
  }
  else {
    pPoint->color[3] = 0.0f;
  }

  if (mix_e_color[3]) {
    pPoint->e_color[3] = mix_e_color[3] * divisor;
    mul_v3_v3fl(pPoint->e_color, mix_e_color, divisor / pPoint->e_color[3]);
  }
  else {
    pPoint->e_color[3] = 0.0f;
  }

  pPoint->wetness = mix_wetness / numOfNeighs;
}

static void dynamicPaint_doBorderStep(DynamicPaintSurface *surface)
{
  PaintSurfaceData *sData = surface->data;

  if (!sData->adj_data || !sData->adj_data->border) {
    return;
  }

  /* Don't use prevPoint, relying on the condition that neighbors are never border pixels. */
  DynamicPaintEffectData data = {
      .surface = surface,
  };

  TaskParallelSettings settings;
  BLI_parallel_range_settings_defaults(&settings);
  settings.use_threading = (sData->adj_data->total_border > 1000);
  BLI_task_parallel_range(
      0, sData->adj_data->total_border, &data, dynamic_paint_border_cb, &settings);
}

static void dynamic_paint_wave_step_cb(void *__restrict userdata,
                                       const int index,
                                       const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintEffectData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;
  BakeAdjPoint *bNeighs = sData->bData->bNeighs;
  const PaintWavePoint *prevPoint = data->prevPoint;

  const float wave_speed = data->wave_speed;
  const float wave_scale = data->wave_scale;
  const float wave_max_slope = data->wave_max_slope;

  const float dt = data->dt;
  const float min_dist = data->min_dist;
  const float damp_factor = data->damp_factor;

  PaintWavePoint *wPoint = &((PaintWavePoint *)sData->type_data)[index];
  const int numOfNeighs = sData->adj_data->n_num[index];
  float force = 0.0f, avg_dist = 0.0f, avg_height = 0.0f, avg_n_height = 0.0f;
  int numOfN = 0, numOfRN = 0;

  if (wPoint->state > 0) {
    return;
  }

  const int *n_index = sData->adj_data->n_index;
  const int *n_target = sData->adj_data->n_target;
  const int *adj_flags = sData->adj_data->flags;

  /* calculate force from surrounding points */
  for (int i = 0; i < numOfNeighs; i++) {
    const int n_idx = n_index[index] + i;
    float dist = bNeighs[n_idx].dist * wave_scale;
    const PaintWavePoint *tPoint = &prevPoint[n_target[n_idx]];

    if (!dist || tPoint->state > 0) {
      continue;
    }

    CLAMP_MIN(dist, min_dist);
    avg_dist += dist;
    numOfN++;

    /* count average height for edge points for open borders */
    if (!(adj_flags[n_target[n_idx]] & ADJ_ON_MESH_EDGE)) {
      avg_n_height += tPoint->height;
      numOfRN++;
    }

    force += (tPoint->height - wPoint->height) / (dist * dist);
    avg_height += tPoint->height;
  }
  avg_dist = (numOfN) ? avg_dist / numOfN : 0.0f;

  if (surface->flags & MOD_DPAINT_WAVE_OPEN_BORDERS && adj_flags[index] & ADJ_ON_MESH_EDGE) {
    /* if open borders, apply a fake height to keep waves going on */
    avg_n_height = (numOfRN) ? avg_n_height / numOfRN : 0.0f;
    wPoint->height = (dt * wave_speed * avg_n_height + wPoint->height * avg_dist) /
                     (avg_dist + dt * wave_speed);
  }
  /* else do wave eq */
  else {
    /* add force towards zero height based on average dist */
    if (avg_dist) {
      force += (0.0f - wPoint->height) * surface->wave_spring / (avg_dist * avg_dist) / 2.0f;
    }

    /* change point velocity */
    wPoint->velocity += force * dt * wave_speed * wave_speed;
    /* damping */
    wPoint->velocity *= damp_factor;
    /* and new height */
    wPoint->height += wPoint->velocity * dt;

    /* limit wave slope steepness */
    if (wave_max_slope && avg_dist) {
      const float max_offset = wave_max_slope * avg_dist;
      const float offset = (numOfN) ? (avg_height / numOfN - wPoint->height) : 0.0f;
      if (offset > max_offset) {
        wPoint->height += offset - max_offset;
      }
      else if (offset < -max_offset) {
        wPoint->height += offset + max_offset;
      }
    }
  }

  if (data->reset_wave) {
    /* if there wasn't any brush intersection, clear isect height */
    if (wPoint->state == DPAINT_WAVE_NONE) {
      wPoint->brush_isect = 0.0f;
    }
    wPoint->state = DPAINT_WAVE_NONE;
  }
}

static void dynamicPaint_doWaveStep(DynamicPaintSurface *surface, float timescale)
{
  PaintSurfaceData *sData = surface->data;
  BakeAdjPoint *bNeighs = sData->bData->bNeighs;
  int index;
  int steps, ss;
  float dt, min_dist, damp_factor;
  const float wave_speed = surface->wave_speed;
  const float wave_max_slope = (surface->wave_smoothness >= 0.01f) ?
                                   (0.5f / surface->wave_smoothness) :
                                   0.0f;
  double average_dist = 0.0f;
  const float canvas_size = getSurfaceDimension(sData);
  const float wave_scale = CANVAS_REL_SIZE / canvas_size;

  /* allocate memory */
  PaintWavePoint *prevPoint = MEM_mallocN(sData->total_points * sizeof(PaintWavePoint), __func__);
  if (!prevPoint) {
    return;
  }

  /* calculate average neigh distance (single thread) */
  for (index = 0; index < sData->total_points; index++) {
    int numOfNeighs = sData->adj_data->n_num[index];

    for (int i = 0; i < numOfNeighs; i++) {
      average_dist += (double)bNeighs[sData->adj_data->n_index[index] + i].dist;
    }
  }
  average_dist *= (double)wave_scale / sData->adj_data->total_targets;

  /* determine number of required steps */
  steps = (int)ceil((double)(WAVE_TIME_FAC * timescale * surface->wave_timescale) /
                    (average_dist / (double)wave_speed / 3));
  CLAMP(steps, 1, 20);
  timescale /= steps;

  /* apply simulation values for final timescale */
  dt = WAVE_TIME_FAC * timescale * surface->wave_timescale;
  min_dist = wave_speed * dt * 1.5f;
  damp_factor = pow((1.0f - surface->wave_damping), timescale * surface->wave_timescale);

  for (ss = 0; ss < steps; ss++) {
    /* copy previous frame data */
    memcpy(prevPoint, sData->type_data, sData->total_points * sizeof(PaintWavePoint));

    DynamicPaintEffectData data = {
        .surface = surface,
        .prevPoint = prevPoint,
        .wave_speed = wave_speed,
        .wave_scale = wave_scale,
        .wave_max_slope = wave_max_slope,
        .dt = dt,
        .min_dist = min_dist,
        .damp_factor = damp_factor,
        .reset_wave = (ss == steps - 1),
    };
    TaskParallelSettings settings;
    BLI_parallel_range_settings_defaults(&settings);
    settings.use_threading = (sData->total_points > 1000);
    BLI_task_parallel_range(0, sData->total_points, &data, dynamic_paint_wave_step_cb, &settings);
  }

  MEM_freeN(prevPoint);
}

/* Do dissolve and fading effects */
static bool dynamic_paint_surface_needs_dry_dissolve(DynamicPaintSurface *surface)
{
  return (((surface->type == MOD_DPAINT_SURFACE_T_PAINT) &&
           (surface->flags & (MOD_DPAINT_USE_DRYING | MOD_DPAINT_DISSOLVE))) ||
          (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WEIGHT) &&
           (surface->flags & MOD_DPAINT_DISSOLVE)));
}

typedef struct DynamicPaintDissolveDryData {
  const DynamicPaintSurface *surface;
  const float timescale;
} DynamicPaintDissolveDryData;

static void dynamic_paint_surface_pre_step_cb(void *__restrict userdata,
                                              const int index,
                                              const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintDissolveDryData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;
  const float timescale = data->timescale;

  /* Do drying dissolve effects */
  if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
    PaintPoint *pPoint = &((PaintPoint *)sData->type_data)[index];
    /* drying */
    if (surface->flags & MOD_DPAINT_USE_DRYING) {
      if (pPoint->wetness >= MIN_WETNESS) {
        float f_color[4];
        float p_wetness = pPoint->wetness;

        value_dissolve(&pPoint->wetness,
                       surface->dry_speed,
                       timescale,
                       (surface->flags & MOD_DPAINT_DRY_LOG) != 0);
        CLAMP_MIN(pPoint->wetness, 0.0f);

        if (pPoint->wetness < surface->color_dry_threshold) {
          float dry_ratio = pPoint->wetness / p_wetness;

          /*
           * Slowly "shift" paint from wet layer to dry layer as it drys:
           */
          /* make sure alpha values are within proper range */
          CLAMP(pPoint->color[3], 0.0f, 1.0f);
          CLAMP(pPoint->e_color[3], 0.0f, 1.0f);

          /* get current final blended color of these layers */
          blendColors(
              pPoint->color, pPoint->color[3], pPoint->e_color, pPoint->e_color[3], f_color);
          /* reduce wet layer alpha by dry factor */
          pPoint->e_color[3] *= dry_ratio;

          /* Now calculate new alpha for dry layer that keeps final blended color unchanged. */
          pPoint->color[3] = (f_color[3] - pPoint->e_color[3]) / (1.0f - pPoint->e_color[3]);
          /* For each rgb component, calculate a new dry layer color that keeps the final blend
           * color with these new alpha values. (wet layer color doesn't change). */
          if (pPoint->color[3]) {
            for (int i = 0; i < 3; i++) {
              pPoint->color[i] = (f_color[i] * f_color[3] -
                                  pPoint->e_color[i] * pPoint->e_color[3]) /
                                 (pPoint->color[3] * (1.0f - pPoint->e_color[3]));
            }
          }
        }

        pPoint->state = DPAINT_PAINT_WET;
      }
      /* in case of just dryed paint, just mix it to the dry layer and mark it empty */
      else if (pPoint->state > 0) {
        float f_color[4];
        blendColors(pPoint->color, pPoint->color[3], pPoint->e_color, pPoint->e_color[3], f_color);
        copy_v4_v4(pPoint->color, f_color);
        /* clear wet layer */
        pPoint->wetness = 0.0f;
        pPoint->e_color[3] = 0.0f;
        pPoint->state = DPAINT_PAINT_DRY;
      }
    }

    if (surface->flags & MOD_DPAINT_DISSOLVE) {
      value_dissolve(&pPoint->color[3],
                     surface->diss_speed,
                     timescale,
                     (surface->flags & MOD_DPAINT_DISSOLVE_LOG) != 0);
      CLAMP_MIN(pPoint->color[3], 0.0f);

      value_dissolve(&pPoint->e_color[3],
                     surface->diss_speed,
                     timescale,
                     (surface->flags & MOD_DPAINT_DISSOLVE_LOG) != 0);
      CLAMP_MIN(pPoint->e_color[3], 0.0f);
    }
  }
  /* dissolve for float types */
  else if (surface->flags & MOD_DPAINT_DISSOLVE &&
           ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WEIGHT)) {
    float *point = &((float *)sData->type_data)[index];
    /* log or linear */
    value_dissolve(
        point, surface->diss_speed, timescale, (surface->flags & MOD_DPAINT_DISSOLVE_LOG) != 0);
    CLAMP_MIN(*point, 0.0f);
  }
}

static bool dynamicPaint_surfaceHasMoved(DynamicPaintSurface *surface, Object *ob)
{
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  Mesh *mesh = dynamicPaint_canvas_mesh_get(surface->canvas);
  MVert *mvert = mesh->mvert;

  int numOfVerts = mesh->totvert;

  if (!bData->prev_verts) {
    return true;
  }

  /* matrix comparison */
  if (!equals_m4m4(bData->prev_obmat, ob->obmat)) {
    return true;
  }

  /* vertices */
  for (int i = 0; i < numOfVerts; i++) {
    if (!equals_v3v3(bData->prev_verts[i].co, mvert[i].co)) {
      return true;
    }
  }

  return false;
}

/* Prepare for surface step by creating PaintBakeNormal data */
typedef struct DynamicPaintGenerateBakeData {
  const DynamicPaintSurface *surface;
  Object *ob;

  const MVert *mvert;
  const float (*vert_normals)[3];
  const Vec3f *canvas_verts;

  const bool do_velocity_data;
  const bool new_bdata;
} DynamicPaintGenerateBakeData;

static void dynamic_paint_generate_bake_data_cb(void *__restrict userdata,
                                                const int index,
                                                const TaskParallelTLS *__restrict UNUSED(tls))
{
  const DynamicPaintGenerateBakeData *data = userdata;

  const DynamicPaintSurface *surface = data->surface;
  const PaintSurfaceData *sData = surface->data;
  const PaintAdjData *adj_data = sData->adj_data;
  const PaintBakeData *bData = sData->bData;

  Object *ob = data->ob;

  const Vec3f *canvas_verts = data->canvas_verts;

  const bool do_velocity_data = data->do_velocity_data;
  const bool new_bdata = data->new_bdata;

  float prev_point[3] = {0.0f, 0.0f, 0.0f};
  float temp_nor[3];

  if (do_velocity_data && !new_bdata) {
    copy_v3_v3(prev_point, bData->realCoord[bData->s_pos[index]].v);
  }

  /*
   * Calculate current 3D-position and normal of each surface point
   */
  if (surface->format == MOD_DPAINT_SURFACE_F_IMAGESEQ) {
    float n1[3], n2[3], n3[3];
    const ImgSeqFormatData *f_data = (ImgSeqFormatData *)sData->format_data;
    const PaintUVPoint *tPoint = &((PaintUVPoint *)f_data->uv_p)[index];

    bData->s_num[index] = (surface->flags & MOD_DPAINT_ANTIALIAS) ? 5 : 1;
    bData->s_pos[index] = index * bData->s_num[index];

    /* per sample coordinates */
    for (int ss = 0; ss < bData->s_num[index]; ss++) {
      interp_v3_v3v3v3(bData->realCoord[bData->s_pos[index] + ss].v,
                       canvas_verts[tPoint->v1].v,
                       canvas_verts[tPoint->v2].v,
                       canvas_verts[tPoint->v3].v,
                       f_data->barycentricWeights[index * bData->s_num[index] + ss].v);
    }

    /* Calculate current pixel surface normal */
    copy_v3_v3(n1, data->vert_normals[tPoint->v1]);
    copy_v3_v3(n2, data->vert_normals[tPoint->v2]);
    copy_v3_v3(n3, data->vert_normals[tPoint->v3]);

    interp_v3_v3v3v3(
        temp_nor, n1, n2, n3, f_data->barycentricWeights[index * bData->s_num[index]].v);
    normalize_v3(temp_nor);
    if (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) {
      /* Prepare surface normal directional scale to easily convert
       * brush intersection amount between global and local space */
      float scaled_nor[3];
      mul_v3_v3v3(scaled_nor, temp_nor, ob->scale);
      bData->bNormal[index].normal_scale = len_v3(scaled_nor);
    }
    mul_mat3_m4_v3(ob->obmat, temp_nor);
    normalize_v3(temp_nor);
    negate_v3_v3(bData->bNormal[index].invNorm, temp_nor);
  }
  else if (surface->format == MOD_DPAINT_SURFACE_F_VERTEX) {
    int ss;
    if (surface->flags & MOD_DPAINT_ANTIALIAS && adj_data) {
      bData->s_num[index] = adj_data->n_num[index] + 1;
      bData->s_pos[index] = adj_data->n_index[index] + index;
    }
    else {
      bData->s_num[index] = 1;
      bData->s_pos[index] = index;
    }

    /* calculate position for each sample */
    for (ss = 0; ss < bData->s_num[index]; ss++) {
      /* first sample is always point center */
      copy_v3_v3(bData->realCoord[bData->s_pos[index] + ss].v, canvas_verts[index].v);
      if (ss > 0) {
        int t_index = adj_data->n_index[index] + (ss - 1);
        /* get vertex position at 1/3 of each neigh edge */
        mul_v3_fl(bData->realCoord[bData->s_pos[index] + ss].v, 2.0f / 3.0f);
        madd_v3_v3fl(bData->realCoord[bData->s_pos[index] + ss].v,
                     canvas_verts[adj_data->n_target[t_index]].v,
                     1.0f / 3.0f);
      }
    }

    /* normal */
    copy_v3_v3(temp_nor, data->vert_normals[index]);
    if (ELEM(surface->type, MOD_DPAINT_SURFACE_T_DISPLACE, MOD_DPAINT_SURFACE_T_WAVE)) {
      /* Prepare surface normal directional scale to easily convert
       * brush intersection amount between global and local space */
      float scaled_nor[3];
      mul_v3_v3v3(scaled_nor, temp_nor, ob->scale);
      bData->bNormal[index].normal_scale = len_v3(scaled_nor);
    }
    mul_mat3_m4_v3(ob->obmat, temp_nor);
    normalize_v3(temp_nor);
    negate_v3_v3(bData->bNormal[index].invNorm, temp_nor);
  }

  /* calculate speed vector */
  if (do_velocity_data && !new_bdata && !bData->clear) {
    sub_v3_v3v3(bData->velocity[index].v, bData->realCoord[bData->s_pos[index]].v, prev_point);
  }
}

static bool dynamicPaint_generateBakeData(DynamicPaintSurface *surface,
                                          Depsgraph *depsgraph,
                                          Object *ob)
{
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  Mesh *mesh = dynamicPaint_canvas_mesh_get(surface->canvas);
  int index;
  bool new_bdata = false;
  const bool do_velocity_data = ((surface->effect & MOD_DPAINT_EFFECT_DO_DRIP) ||
                                 (surface_getBrushFlags(surface, depsgraph) &
                                  BRUSH_USES_VELOCITY));
  const bool do_accel_data = (surface->effect & MOD_DPAINT_EFFECT_DO_DRIP) != 0;

  int canvasNumOfVerts = mesh->totvert;
  MVert *mvert = mesh->mvert;
  Vec3f *canvas_verts;

  if (bData) {
    const bool surface_moved = dynamicPaint_surfaceHasMoved(surface, ob);

    /* Get previous speed for acceleration. */
    if (do_accel_data && bData->prev_velocity && bData->velocity) {
      memcpy(bData->prev_velocity, bData->velocity, sData->total_points * sizeof(Vec3f));
    }

    /* reset speed vectors */
    if (do_velocity_data && bData->velocity && (bData->clear || !surface_moved)) {
      memset(bData->velocity, 0, sData->total_points * sizeof(Vec3f));
    }

    /* if previous data exists and mesh hasn't moved, no need to recalc */
    if (!surface_moved) {
      return true;
    }
  }

  canvas_verts = (struct Vec3f *)MEM_mallocN(canvasNumOfVerts * sizeof(struct Vec3f),
                                             "Dynamic Paint transformed canvas verts");
  if (!canvas_verts) {
    return false;
  }

  /* allocate memory if required */
  if (!bData) {
    sData->bData = bData = (struct PaintBakeData *)MEM_callocN(sizeof(struct PaintBakeData),
                                                               "Dynamic Paint bake data");
    if (!bData) {
      if (canvas_verts) {
        MEM_freeN(canvas_verts);
      }
      return false;
    }

    /* Init bdata */
    bData->bNormal = (struct PaintBakeNormal *)MEM_mallocN(
        sData->total_points * sizeof(struct PaintBakeNormal), "Dynamic Paint step data");
    bData->s_pos = MEM_mallocN(sData->total_points * sizeof(unsigned int),
                               "Dynamic Paint bData s_pos");
    bData->s_num = MEM_mallocN(sData->total_points * sizeof(unsigned int),
                               "Dynamic Paint bData s_num");
    bData->realCoord = (struct Vec3f *)MEM_mallocN(surface_totalSamples(surface) * sizeof(Vec3f),
                                                   "Dynamic Paint point coords");
    bData->prev_verts = MEM_mallocN(canvasNumOfVerts * sizeof(MVert),
                                    "Dynamic Paint bData prev_verts");

    /* if any allocation failed, free everything */
    if (!bData->bNormal || !bData->s_pos || !bData->s_num || !bData->realCoord || !canvas_verts) {
      if (bData->bNormal) {
        MEM_freeN(bData->bNormal);
      }
      if (bData->s_pos) {
        MEM_freeN(bData->s_pos);
      }
      if (bData->s_num) {
        MEM_freeN(bData->s_num);
      }
      if (bData->realCoord) {
        MEM_freeN(bData->realCoord);
      }
      if (canvas_verts) {
        MEM_freeN(canvas_verts);
      }

      return setError(surface->canvas, N_("Not enough free memory"));
    }

    new_bdata = true;
  }

  if (do_velocity_data && !bData->velocity) {
    bData->velocity = (struct Vec3f *)MEM_callocN(sData->total_points * sizeof(Vec3f),
                                                  "Dynamic Paint velocity");
  }
  if (do_accel_data && !bData->prev_velocity) {
    bData->prev_velocity = (struct Vec3f *)MEM_mallocN(sData->total_points * sizeof(Vec3f),
                                                       "Dynamic Paint prev velocity");
    /* copy previous vel */
    if (bData->prev_velocity && bData->velocity) {
      memcpy(bData->prev_velocity, bData->velocity, sData->total_points * sizeof(Vec3f));
    }
  }

  /*
   * Make a transformed copy of canvas derived mesh vertices to avoid recalculation.
   */
  bData->mesh_bounds.valid = false;
  for (index = 0; index < canvasNumOfVerts; index++) {
    copy_v3_v3(canvas_verts[index].v, mvert[index].co);
    mul_m4_v3(ob->obmat, canvas_verts[index].v);
    boundInsert(&bData->mesh_bounds, canvas_verts[index].v);
  }

  /*
   * Prepare each surface point for a new step
   */
  DynamicPaintGenerateBakeData data = {
      .surface = surface,
      .ob = ob,
      .mvert = mvert,
      .vert_normals = BKE_mesh_vertex_normals_ensure(mesh),
      .canvas_verts = canvas_verts,
      .do_velocity_data = do_velocity_data,
      .new_bdata = new_bdata,
  };
  TaskParallelSettings settings;
  BLI_parallel_range_settings_defaults(&settings);
  settings.use_threading = (sData->total_points > 1000);
  BLI_task_parallel_range(
      0, sData->total_points, &data, dynamic_paint_generate_bake_data_cb, &settings);

  MEM_freeN(canvas_verts);

  /* generate surface space partitioning grid */
  surfaceGenerateGrid(surface);
  /* calculate current frame adjacency point distances and global dirs */
  dynamicPaint_prepareAdjacencyData(surface, false);

  /* Copy current frame vertices to check against in next frame */
  copy_m4_m4(bData->prev_obmat, ob->obmat);
  memcpy(bData->prev_verts, mvert, canvasNumOfVerts * sizeof(MVert));

  bData->clear = 0;

  return true;
}

/*
 * Do Dynamic Paint step. Paints scene brush objects of current state/frame to the surface.
 */
static int dynamicPaint_doStep(Depsgraph *depsgraph,
                               Scene *scene,
                               Object *ob,
                               DynamicPaintSurface *surface,
                               float timescale,
                               float subframe)
{
  PaintSurfaceData *sData = surface->data;
  PaintBakeData *bData = sData->bData;
  DynamicPaintCanvasSettings *canvas = surface->canvas;
  const bool for_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER);
  int ret = 1;

  if (sData->total_points < 1) {
    return 0;
  }

  if (dynamic_paint_surface_needs_dry_dissolve(surface)) {
    DynamicPaintDissolveDryData data = {
        .surface = surface,
        .timescale = timescale,
    };
    TaskParallelSettings settings;
    BLI_parallel_range_settings_defaults(&settings);
    settings.use_threading = (sData->total_points > 1000);
    BLI_task_parallel_range(
        0, sData->total_points, &data, dynamic_paint_surface_pre_step_cb, &settings);
  }

  /*
   * Loop through surface's target paint objects and do painting
   */
  {
    unsigned int numobjects;
    Object **objects = BKE_collision_objects_create(
        depsgraph, NULL, surface->brush_group, &numobjects, eModifierType_DynamicPaint);

    /* backup current scene frame */
    int scene_frame = scene->r.cfra;
    float scene_subframe = scene->r.subframe;

    for (int i = 0; i < numobjects; i++) {
      Object *brushObj = objects[i];

      /* check if target has an active dp modifier */
      ModifierData *md = BKE_modifiers_findby_type(brushObj, eModifierType_DynamicPaint);
      if (md && md->mode & (eModifierMode_Realtime | eModifierMode_Render)) {
        DynamicPaintModifierData *pmd2 = (DynamicPaintModifierData *)md;
        /* make sure we're dealing with a brush */
        if (pmd2->brush && pmd2->type == MOD_DYNAMICPAINT_TYPE_BRUSH) {
          DynamicPaintBrushSettings *brush = pmd2->brush;

          /* calculate brush speed vectors if required */
          if (surface->type == MOD_DPAINT_SURFACE_T_PAINT && brush->flags & MOD_DPAINT_DO_SMUDGE) {
            bData->brush_velocity = MEM_callocN(sizeof(float[4]) * sData->total_points,
                                                "Dynamic Paint brush velocity");
            /* init adjacency data if not already */
            if (!sData->adj_data) {
              dynamicPaint_initAdjacencyData(surface, true);
            }
            if (!bData->bNeighs) {
              dynamicPaint_prepareAdjacencyData(surface, true);
            }
          }

          /* update object data on this subframe */
          if (subframe) {
            scene_setSubframe(scene, subframe);
            BKE_object_modifier_update_subframe(depsgraph,
                                                scene,
                                                brushObj,
                                                true,
                                                SUBFRAME_RECURSION,
                                                BKE_scene_ctime_get(scene),
                                                eModifierType_DynamicPaint);
          }

          /* Apply brush on the surface depending on its collision type */
          if (brush->psys && brush->psys->part &&
              ELEM(brush->psys->part->type,
                   PART_EMITTER,
                   PART_FLUID,
                   PART_FLUID_FLIP,
                   PART_FLUID_SPRAY,
                   PART_FLUID_BUBBLE,
                   PART_FLUID_FOAM,
                   PART_FLUID_TRACER,
                   PART_FLUID_SPRAYFOAM,
                   PART_FLUID_SPRAYBUBBLE,
                   PART_FLUID_FOAMBUBBLE,
                   PART_FLUID_SPRAYFOAMBUBBLE) &&
              psys_check_enabled(brushObj, brush->psys, for_render)) {
            /* Paint a particle system */
            dynamicPaint_paintParticles(surface, brush->psys, brush, timescale);
          }
          /* Object center distance: */
          if (brush->collision == MOD_DPAINT_COL_POINT && brushObj != ob) {
            dynamicPaint_paintSinglePoint(
                depsgraph, surface, brushObj->loc, brush, brushObj, scene, timescale);
          }
          /* Mesh volume/proximity: */
          else if (brushObj != ob) {
            dynamicPaint_paintMesh(depsgraph, surface, brush, brushObj, scene, timescale);
          }

          /* reset object to its original state */
          if (subframe) {
            scene->r.cfra = scene_frame;
            scene->r.subframe = scene_subframe;
            BKE_object_modifier_update_subframe(depsgraph,
                                                scene,
                                                brushObj,
                                                true,
                                                SUBFRAME_RECURSION,
                                                BKE_scene_ctime_get(scene),
                                                eModifierType_DynamicPaint);
          }

          /* process special brush effects, like smudge */
          if (bData->brush_velocity) {
            if (surface->type == MOD_DPAINT_SURFACE_T_PAINT &&
                brush->flags & MOD_DPAINT_DO_SMUDGE) {
              dynamicPaint_doSmudge(surface, brush, timescale);
            }
            MEM_freeN(bData->brush_velocity);
            bData->brush_velocity = NULL;
          }
        }
      }
    }

    BKE_collision_objects_free(objects);
  }

  /* surfaces operations that use adjacency data */
  if (sData->adj_data && bData->bNeighs) {
    /* wave type surface simulation step */
    if (surface->type == MOD_DPAINT_SURFACE_T_WAVE) {
      dynamicPaint_doWaveStep(surface, timescale);
    }

    /* paint surface effects */
    if (surface->effect && surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      int steps = 1, s;
      PaintPoint *prevPoint;
      float *force = NULL;

      /* Allocate memory for surface previous points to read unchanged values from */
      prevPoint = MEM_mallocN(sData->total_points * sizeof(struct PaintPoint),
                              "PaintSurfaceDataCopy");
      if (!prevPoint) {
        return setError(canvas, N_("Not enough free memory"));
      }

      /* Prepare effects and get number of required steps */
      steps = dynamicPaint_prepareEffectStep(depsgraph, surface, scene, ob, &force, timescale);
      for (s = 0; s < steps; s++) {
        dynamicPaint_doEffectStep(surface, force, prevPoint, timescale, (float)steps);
      }

      /* Free temporary effect data */
      if (prevPoint) {
        MEM_freeN(prevPoint);
      }
      if (force) {
        MEM_freeN(force);
      }
    }

    /* paint island border pixels */
    if (surface->type == MOD_DPAINT_SURFACE_T_PAINT) {
      dynamicPaint_doBorderStep(surface);
    }
  }

  return ret;
}

int dynamicPaint_calculateFrame(DynamicPaintSurface *surface,
                                struct Depsgraph *depsgraph,
                                Scene *scene,
                                Object *cObject,
                                int frame)
{
  float timescale = 1.0f;

  /* apply previous displace on derivedmesh if incremental surface */
  if (surface->flags & MOD_DPAINT_DISP_INCREMENTAL) {
    dynamicPaint_applySurfaceDisplace(surface, dynamicPaint_canvas_mesh_get(surface->canvas));
  }

  /* update bake data */
  dynamicPaint_generateBakeData(surface, depsgraph, cObject);

  /* don't do substeps for first frame */
  if (surface->substeps && (frame != surface->start_frame)) {
    int st;
    timescale = 1.0f / (surface->substeps + 1);

    for (st = 1; st <= surface->substeps; st++) {
      float subframe = ((float)st) / (surface->substeps + 1);
      if (!dynamicPaint_doStep(depsgraph, scene, cObject, surface, timescale, subframe)) {
        return 0;
      }
    }
  }

  return dynamicPaint_doStep(depsgraph, scene, cObject, surface, timescale, 0.0f);
}
