452 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			452 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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.
 | 
						|
 *
 | 
						|
 * The Original Code is Copyright (C) 2008, Blender Foundation
 | 
						|
 * This is a new part of Blender
 | 
						|
 */
 | 
						|
 | 
						|
/** \file
 | 
						|
 * \ingroup bke
 | 
						|
 */
 | 
						|
 | 
						|
#include <math.h>
 | 
						|
#include <stddef.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include "CLG_log.h"
 | 
						|
 | 
						|
#include "MEM_guardedalloc.h"
 | 
						|
 | 
						|
#include "BLI_blenlib.h"
 | 
						|
#include "BLI_math_vector.h"
 | 
						|
 | 
						|
#include "BLT_translation.h"
 | 
						|
 | 
						|
#include "DNA_gpencil_types.h"
 | 
						|
 | 
						|
#include "BKE_collection.h"
 | 
						|
#include "BKE_curve.h"
 | 
						|
#include "BKE_gpencil.h"
 | 
						|
#include "BKE_gpencil_curve.h"
 | 
						|
#include "BKE_gpencil_geom.h"
 | 
						|
#include "BKE_main.h"
 | 
						|
#include "BKE_material.h"
 | 
						|
#include "BKE_object.h"
 | 
						|
 | 
						|
#include "DEG_depsgraph_query.h"
 | 
						|
 | 
						|
/* Helper: Check materials with same color. */
 | 
						|
static int gpencil_check_same_material_color(Object *ob_gp, float color[4], Material **r_mat)
 | 
						|
{
 | 
						|
  Material *ma = NULL;
 | 
						|
  float color_cu[4];
 | 
						|
  linearrgb_to_srgb_v3_v3(color_cu, color);
 | 
						|
  float hsv1[4];
 | 
						|
  rgb_to_hsv_v(color_cu, hsv1);
 | 
						|
  hsv1[3] = color[3];
 | 
						|
 | 
						|
  for (int i = 1; i <= ob_gp->totcol; i++) {
 | 
						|
    ma = BKE_object_material_get(ob_gp, i);
 | 
						|
    MaterialGPencilStyle *gp_style = ma->gp_style;
 | 
						|
    /* Check color with small tolerance (better in HSV). */
 | 
						|
    float hsv2[4];
 | 
						|
    rgb_to_hsv_v(gp_style->fill_rgba, hsv2);
 | 
						|
    hsv2[3] = gp_style->fill_rgba[3];
 | 
						|
    if ((gp_style->fill_style == GP_MATERIAL_FILL_STYLE_SOLID) &&
 | 
						|
        (compare_v4v4(hsv1, hsv2, 0.01f))) {
 | 
						|
      *r_mat = ma;
 | 
						|
      return i - 1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  *r_mat = NULL;
 | 
						|
  return -1;
 | 
						|
}
 | 
						|
 | 
						|
/* Helper: Add gpencil material using curve material as base. */
 | 
						|
static Material *gpencil_add_from_curve_material(Main *bmain,
 | 
						|
                                                 Object *ob_gp,
 | 
						|
                                                 const float cu_color[4],
 | 
						|
                                                 const bool gpencil_lines,
 | 
						|
                                                 const bool fill,
 | 
						|
                                                 int *r_idx)
 | 
						|
{
 | 
						|
  Material *mat_gp = BKE_gpencil_object_material_new(
 | 
						|
      bmain, ob_gp, (fill) ? "Material" : "Unassigned", r_idx);
 | 
						|
  MaterialGPencilStyle *gp_style = mat_gp->gp_style;
 | 
						|
 | 
						|
  /* Stroke color. */
 | 
						|
  if (gpencil_lines) {
 | 
						|
    ARRAY_SET_ITEMS(gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f);
 | 
						|
    gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    linearrgb_to_srgb_v4(gp_style->stroke_rgba, cu_color);
 | 
						|
    gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Fill color. */
 | 
						|
  linearrgb_to_srgb_v4(gp_style->fill_rgba, cu_color);
 | 
						|
  /* Fill is false if the original curve hasn't material assigned, so enable it. */
 | 
						|
  if (fill) {
 | 
						|
    gp_style->flag |= GP_MATERIAL_FILL_SHOW;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Check at least one is enabled. */
 | 
						|
  if (((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0) &&
 | 
						|
      ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) {
 | 
						|
    gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
 | 
						|
  }
 | 
						|
 | 
						|
  return mat_gp;
 | 
						|
}
 | 
						|
 | 
						|
/* Helper: Create new stroke section. */
 | 
						|
static void gpencil_add_new_points(bGPDstroke *gps,
 | 
						|
                                   float *coord_array,
 | 
						|
                                   float pressure,
 | 
						|
                                   int init,
 | 
						|
                                   int totpoints,
 | 
						|
                                   const float init_co[3],
 | 
						|
                                   bool last)
 | 
						|
{
 | 
						|
  for (int i = 0; i < totpoints; i++) {
 | 
						|
    bGPDspoint *pt = &gps->points[i + init];
 | 
						|
    copy_v3_v3(&pt->x, &coord_array[3 * i]);
 | 
						|
    /* Be sure the last point is not on top of the first point of the curve or
 | 
						|
     * the close of the stroke will produce glitches. */
 | 
						|
    if ((last) && (i > 0) && (i == totpoints - 1)) {
 | 
						|
      float dist = len_v3v3(init_co, &pt->x);
 | 
						|
      if (dist < 0.1f) {
 | 
						|
        /* Interpolate between previous point and current to back slightly. */
 | 
						|
        bGPDspoint *pt_prev = &gps->points[i + init - 1];
 | 
						|
        interp_v3_v3v3(&pt->x, &pt_prev->x, &pt->x, 0.95f);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    pt->pressure = pressure;
 | 
						|
    pt->strength = 1.0f;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/* Helper: Get the first collection that includes the object. */
 | 
						|
static Collection *gpencil_get_parent_collection(Scene *scene, Object *ob)
 | 
						|
{
 | 
						|
  Collection *mycol = NULL;
 | 
						|
  FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) {
 | 
						|
    LISTBASE_FOREACH (CollectionObject *, cob, &collection->gobject) {
 | 
						|
      if ((mycol == NULL) && (cob->ob == ob)) {
 | 
						|
        mycol = collection;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  FOREACH_SCENE_COLLECTION_END;
 | 
						|
 | 
						|
  return mycol;
 | 
						|
}
 | 
						|
 | 
						|
/* Helper: Convert one spline to grease pencil stroke. */
 | 
						|
static void gpencil_convert_spline(Main *bmain,
 | 
						|
                                   Object *ob_gp,
 | 
						|
                                   Object *ob_cu,
 | 
						|
                                   const bool gpencil_lines,
 | 
						|
                                   const bool only_stroke,
 | 
						|
                                   bGPDframe *gpf,
 | 
						|
                                   Nurb *nu)
 | 
						|
{
 | 
						|
  Curve *cu = (Curve *)ob_cu->data;
 | 
						|
  bool cyclic = true;
 | 
						|
 | 
						|
  /* Create Stroke. */
 | 
						|
  bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "bGPDstroke");
 | 
						|
  gps->thickness = 10.0f;
 | 
						|
  gps->fill_opacity_fac = 1.0f;
 | 
						|
  gps->hardeness = 1.0f;
 | 
						|
  gps->uv_scale = 1.0f;
 | 
						|
 | 
						|
  ARRAY_SET_ITEMS(gps->aspect_ratio, 1.0f, 1.0f);
 | 
						|
  ARRAY_SET_ITEMS(gps->caps, GP_STROKE_CAP_ROUND, GP_STROKE_CAP_ROUND);
 | 
						|
  gps->inittime = 0.0f;
 | 
						|
 | 
						|
  gps->flag &= ~GP_STROKE_SELECT;
 | 
						|
  gps->flag |= GP_STROKE_3DSPACE;
 | 
						|
 | 
						|
  gps->mat_nr = 0;
 | 
						|
  /* Count total points
 | 
						|
   * The total of points must consider that last point of each segment is equal to the first
 | 
						|
   * point of next segment.
 | 
						|
   */
 | 
						|
  int totpoints = 0;
 | 
						|
  int segments = 0;
 | 
						|
  int resolu = nu->resolu + 1;
 | 
						|
  segments = nu->pntsu;
 | 
						|
  if ((nu->flagu & CU_NURB_CYCLIC) == 0) {
 | 
						|
    segments--;
 | 
						|
    cyclic = false;
 | 
						|
  }
 | 
						|
  totpoints = (resolu * segments) - (segments - 1);
 | 
						|
 | 
						|
  /* Materials
 | 
						|
   * Notice: The color of the material is the color of viewport and not the final shader color.
 | 
						|
   */
 | 
						|
  Material *mat_gp = NULL;
 | 
						|
  bool fill = true;
 | 
						|
  /* Check if grease pencil has a material with same color.*/
 | 
						|
  float color[4];
 | 
						|
  if ((cu->mat) && (*cu->mat)) {
 | 
						|
    Material *mat_cu = *cu->mat;
 | 
						|
    copy_v4_v4(color, &mat_cu->r);
 | 
						|
  }
 | 
						|
  else {
 | 
						|
    /* Gray (unassigned from SVG add-on) */
 | 
						|
    zero_v4(color);
 | 
						|
    add_v3_fl(color, 0.6f);
 | 
						|
    color[3] = 1.0f;
 | 
						|
    fill = false;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Special case: If the color was created by the SVG add-on and the name contains '_stroke' and
 | 
						|
   * there is only one color, the stroke must not be closed, fill to false and use for
 | 
						|
   * stroke the fill color.
 | 
						|
   */
 | 
						|
  bool do_stroke = false;
 | 
						|
  if (ob_cu->totcol == 1) {
 | 
						|
    Material *ma_stroke = BKE_object_material_get(ob_cu, 1);
 | 
						|
    if ((ma_stroke) && (strstr(ma_stroke->id.name, "_stroke") != NULL)) {
 | 
						|
      do_stroke = true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  int r_idx = gpencil_check_same_material_color(ob_gp, color, &mat_gp);
 | 
						|
  if ((ob_cu->totcol > 0) && (r_idx < 0)) {
 | 
						|
    Material *mat_curve = BKE_object_material_get(ob_cu, 1);
 | 
						|
    mat_gp = gpencil_add_from_curve_material(bmain, ob_gp, color, gpencil_lines, fill, &r_idx);
 | 
						|
 | 
						|
    if ((mat_curve) && (mat_curve->gp_style != NULL)) {
 | 
						|
      MaterialGPencilStyle *gp_style_cur = mat_curve->gp_style;
 | 
						|
      MaterialGPencilStyle *gp_style_gp = mat_gp->gp_style;
 | 
						|
 | 
						|
      copy_v4_v4(gp_style_gp->mix_rgba, gp_style_cur->mix_rgba);
 | 
						|
      gp_style_gp->fill_style = gp_style_cur->fill_style;
 | 
						|
      gp_style_gp->mix_factor = gp_style_cur->mix_factor;
 | 
						|
    }
 | 
						|
 | 
						|
    /* If object has more than 1 material, use second material for stroke color. */
 | 
						|
    if ((!only_stroke) && (ob_cu->totcol > 1) && (BKE_object_material_get(ob_cu, 2))) {
 | 
						|
      mat_curve = BKE_object_material_get(ob_cu, 2);
 | 
						|
      if (mat_curve) {
 | 
						|
        linearrgb_to_srgb_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r);
 | 
						|
        mat_gp->gp_style->stroke_rgba[3] = mat_curve->a;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    else if ((only_stroke) || (do_stroke)) {
 | 
						|
      /* Also use the first color if the fill is none for stroke color. */
 | 
						|
      if (ob_cu->totcol > 0) {
 | 
						|
        mat_curve = BKE_object_material_get(ob_cu, 1);
 | 
						|
        if (mat_curve) {
 | 
						|
          copy_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r);
 | 
						|
          mat_gp->gp_style->stroke_rgba[3] = mat_curve->a;
 | 
						|
          /* Set fill and stroke depending of curve type (3D or 2D). */
 | 
						|
          if ((cu->flag & CU_3D) || ((cu->flag & (CU_FRONT | CU_BACK)) == 0)) {
 | 
						|
            mat_gp->gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
 | 
						|
            mat_gp->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW;
 | 
						|
          }
 | 
						|
          else {
 | 
						|
            mat_gp->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
 | 
						|
            mat_gp->gp_style->flag |= GP_MATERIAL_FILL_SHOW;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  CLAMP_MIN(r_idx, 0);
 | 
						|
 | 
						|
  /* Assign material index to stroke. */
 | 
						|
  gps->mat_nr = r_idx;
 | 
						|
 | 
						|
  /* Add stroke to frame.*/
 | 
						|
  BLI_addtail(&gpf->strokes, gps);
 | 
						|
 | 
						|
  float *coord_array = NULL;
 | 
						|
  float init_co[3];
 | 
						|
 | 
						|
  switch (nu->type) {
 | 
						|
    case CU_POLY: {
 | 
						|
      /* Allocate memory for storage points. */
 | 
						|
      gps->totpoints = nu->pntsu;
 | 
						|
      gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
 | 
						|
      /* Increase thickness for this type. */
 | 
						|
      gps->thickness = 10.0f;
 | 
						|
 | 
						|
      /* Get all curve points */
 | 
						|
      for (int s = 0; s < gps->totpoints; s++) {
 | 
						|
        BPoint *bp = &nu->bp[s];
 | 
						|
        bGPDspoint *pt = &gps->points[s];
 | 
						|
        copy_v3_v3(&pt->x, bp->vec);
 | 
						|
        pt->pressure = bp->radius;
 | 
						|
        pt->strength = 1.0f;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    case CU_BEZIER: {
 | 
						|
      /* Allocate memory for storage points. */
 | 
						|
      gps->totpoints = totpoints;
 | 
						|
      gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
 | 
						|
 | 
						|
      int init = 0;
 | 
						|
      resolu = nu->resolu + 1;
 | 
						|
      segments = nu->pntsu;
 | 
						|
      if ((nu->flagu & CU_NURB_CYCLIC) == 0) {
 | 
						|
        segments--;
 | 
						|
      }
 | 
						|
      /* Get all interpolated curve points of Beziert */
 | 
						|
      for (int s = 0; s < segments; s++) {
 | 
						|
        int inext = (s + 1) % nu->pntsu;
 | 
						|
        BezTriple *prevbezt = &nu->bezt[s];
 | 
						|
        BezTriple *bezt = &nu->bezt[inext];
 | 
						|
        bool last = (bool)(s == segments - 1);
 | 
						|
 | 
						|
        coord_array = MEM_callocN((size_t)3 * resolu * sizeof(float), __func__);
 | 
						|
 | 
						|
        for (int j = 0; j < 3; j++) {
 | 
						|
          BKE_curve_forward_diff_bezier(prevbezt->vec[1][j],
 | 
						|
                                        prevbezt->vec[2][j],
 | 
						|
                                        bezt->vec[0][j],
 | 
						|
                                        bezt->vec[1][j],
 | 
						|
                                        coord_array + j,
 | 
						|
                                        resolu - 1,
 | 
						|
                                        3 * sizeof(float));
 | 
						|
        }
 | 
						|
        /* Save first point coordinates. */
 | 
						|
        if (s == 0) {
 | 
						|
          copy_v3_v3(init_co, &coord_array[0]);
 | 
						|
        }
 | 
						|
        /* Add points to the stroke */
 | 
						|
        gpencil_add_new_points(gps, coord_array, bezt->radius, init, resolu, init_co, last);
 | 
						|
        /* Free memory. */
 | 
						|
        MEM_SAFE_FREE(coord_array);
 | 
						|
 | 
						|
        /* As the last point of segment is the first point of next segment, back one array
 | 
						|
         * element to avoid duplicated points on the same location.
 | 
						|
         */
 | 
						|
        init += resolu - 1;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    case CU_NURBS: {
 | 
						|
      if (nu->pntsv == 1) {
 | 
						|
 | 
						|
        int nurb_points;
 | 
						|
        if (nu->flagu & CU_NURB_CYCLIC) {
 | 
						|
          resolu++;
 | 
						|
          nurb_points = nu->pntsu * resolu;
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          nurb_points = (nu->pntsu - 1) * resolu;
 | 
						|
        }
 | 
						|
        /* Get all curve points. */
 | 
						|
        coord_array = MEM_callocN(sizeof(float[3]) * nurb_points, __func__);
 | 
						|
        BKE_nurb_makeCurve(nu, coord_array, NULL, NULL, NULL, resolu, sizeof(float[3]));
 | 
						|
 | 
						|
        /* Allocate memory for storage points. */
 | 
						|
        gps->totpoints = nurb_points - 1;
 | 
						|
        gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
 | 
						|
 | 
						|
        /* Add points. */
 | 
						|
        gpencil_add_new_points(gps, coord_array, 1.0f, 0, gps->totpoints, init_co, false);
 | 
						|
 | 
						|
        MEM_SAFE_FREE(coord_array);
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    default: {
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  /* Cyclic curve, close stroke. */
 | 
						|
  if ((cyclic) && (!do_stroke)) {
 | 
						|
    BKE_gpencil_stroke_close(gps);
 | 
						|
  }
 | 
						|
 | 
						|
  /* Recalc fill geometry. */
 | 
						|
  BKE_gpencil_stroke_geometry_update(gps);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Convert a curve object to grease pencil stroke.
 | 
						|
 *
 | 
						|
 * \param bmain: Main thread pointer
 | 
						|
 * \param scene: Original scene.
 | 
						|
 * \param ob_gp: Grease pencil object to add strokes.
 | 
						|
 * \param ob_cu: Curve to convert.
 | 
						|
 * \param gpencil_lines: Use lines for strokes.
 | 
						|
 * \param use_collections: Create layers using collection names.
 | 
						|
 * \param only_stroke: The material must be only stroke without fill.
 | 
						|
 */
 | 
						|
void BKE_gpencil_convert_curve(Main *bmain,
 | 
						|
                               Scene *scene,
 | 
						|
                               Object *ob_gp,
 | 
						|
                               Object *ob_cu,
 | 
						|
                               const bool gpencil_lines,
 | 
						|
                               const bool use_collections,
 | 
						|
                               const bool only_stroke)
 | 
						|
{
 | 
						|
  if (ELEM(NULL, ob_gp, ob_cu) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  Curve *cu = (Curve *)ob_cu->data;
 | 
						|
  bGPdata *gpd = (bGPdata *)ob_gp->data;
 | 
						|
  bGPDlayer *gpl = NULL;
 | 
						|
 | 
						|
  /* If the curve is empty, cancel. */
 | 
						|
  if (cu->nurb.first == NULL) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Check if there is an active layer. */
 | 
						|
  if (use_collections) {
 | 
						|
    Collection *collection = gpencil_get_parent_collection(scene, ob_cu);
 | 
						|
    if (collection != NULL) {
 | 
						|
      gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2);
 | 
						|
      if (gpl == NULL) {
 | 
						|
        gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (gpl == NULL) {
 | 
						|
    gpl = BKE_gpencil_layer_active_get(gpd);
 | 
						|
    if (gpl == NULL) {
 | 
						|
      gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* Check if there is an active frame and add if needed. */
 | 
						|
  bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_COPY);
 | 
						|
 | 
						|
  /* Read all splines of the curve and create a stroke for each. */
 | 
						|
  LISTBASE_FOREACH (Nurb *, nu, &cu->nurb) {
 | 
						|
    gpencil_convert_spline(bmain, ob_gp, ob_cu, gpencil_lines, only_stroke, gpf, nu);
 | 
						|
  }
 | 
						|
 | 
						|
  /* Tag for recalculation */
 | 
						|
  DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
 | 
						|
}
 | 
						|
 | 
						|
/** \} */
 |