forked from bartvdbraak/blender
Move some sketching base code in kernel.
Other code will eventually move out of armature editor (to help reuse). This solves the issue reported by Cambo on the ml about kernel code calling editor functions.
This commit is contained in:
parent
78b32ceeed
commit
24a269a07c
157
source/blender/blenkernel/BKE_sketch.h
Normal file
157
source/blender/blenkernel/BKE_sketch.h
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
||||||
|
*
|
||||||
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
* Contributor(s): none yet.
|
||||||
|
*
|
||||||
|
* ***** END GPL LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
#ifndef BKE_SKETCH_H
|
||||||
|
#define BKE_SKETCH_H
|
||||||
|
|
||||||
|
typedef enum SK_PType
|
||||||
|
{
|
||||||
|
PT_CONTINUOUS,
|
||||||
|
PT_EXACT,
|
||||||
|
} SK_PType;
|
||||||
|
|
||||||
|
typedef enum SK_PMode
|
||||||
|
{
|
||||||
|
PT_SNAP,
|
||||||
|
PT_PROJECT,
|
||||||
|
} SK_PMode;
|
||||||
|
|
||||||
|
typedef struct SK_Point
|
||||||
|
{
|
||||||
|
float p[3];
|
||||||
|
short p2d[2];
|
||||||
|
float no[3];
|
||||||
|
float size;
|
||||||
|
SK_PType type;
|
||||||
|
SK_PMode mode;
|
||||||
|
} SK_Point;
|
||||||
|
|
||||||
|
typedef struct SK_Stroke
|
||||||
|
{
|
||||||
|
struct SK_Stroke *next, *prev;
|
||||||
|
|
||||||
|
SK_Point *points;
|
||||||
|
int nb_points;
|
||||||
|
int buf_size;
|
||||||
|
int selected;
|
||||||
|
} SK_Stroke;
|
||||||
|
|
||||||
|
#define SK_OVERDRAW_LIMIT 5
|
||||||
|
|
||||||
|
typedef struct SK_Overdraw
|
||||||
|
{
|
||||||
|
SK_Stroke *target;
|
||||||
|
int start, end;
|
||||||
|
int count;
|
||||||
|
} SK_Overdraw;
|
||||||
|
|
||||||
|
#define SK_Stroke_BUFFER_INIT_SIZE 20
|
||||||
|
|
||||||
|
typedef struct SK_DrawData
|
||||||
|
{
|
||||||
|
short mval[2];
|
||||||
|
short previous_mval[2];
|
||||||
|
SK_PType type;
|
||||||
|
} SK_DrawData;
|
||||||
|
|
||||||
|
typedef struct SK_Intersection
|
||||||
|
{
|
||||||
|
struct SK_Intersection *next, *prev;
|
||||||
|
SK_Stroke *stroke;
|
||||||
|
int before;
|
||||||
|
int after;
|
||||||
|
int gesture_index;
|
||||||
|
float p[3];
|
||||||
|
float lambda; /* used for sorting intersection points */
|
||||||
|
} SK_Intersection;
|
||||||
|
|
||||||
|
typedef struct SK_Sketch
|
||||||
|
{
|
||||||
|
ListBase strokes;
|
||||||
|
ListBase depth_peels;
|
||||||
|
SK_Stroke *active_stroke;
|
||||||
|
SK_Stroke *gesture;
|
||||||
|
SK_Point next_point;
|
||||||
|
SK_Overdraw over;
|
||||||
|
} SK_Sketch;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct SK_Gesture {
|
||||||
|
SK_Stroke *stk;
|
||||||
|
SK_Stroke *segments;
|
||||||
|
|
||||||
|
ListBase intersections;
|
||||||
|
ListBase self_intersections;
|
||||||
|
|
||||||
|
int nb_self_intersections;
|
||||||
|
int nb_intersections;
|
||||||
|
int nb_segments;
|
||||||
|
} SK_Gesture;
|
||||||
|
|
||||||
|
|
||||||
|
/************************************************/
|
||||||
|
|
||||||
|
void freeSketch(SK_Sketch *sketch);
|
||||||
|
SK_Sketch* createSketch();
|
||||||
|
|
||||||
|
void sk_removeStroke(SK_Sketch *sketch, SK_Stroke *stk);
|
||||||
|
|
||||||
|
void sk_freeStroke(SK_Stroke *stk);
|
||||||
|
SK_Stroke* sk_createStroke();
|
||||||
|
|
||||||
|
SK_Point *sk_lastStrokePoint(SK_Stroke *stk);
|
||||||
|
|
||||||
|
void sk_allocStrokeBuffer(SK_Stroke *stk);
|
||||||
|
void sk_shrinkStrokeBuffer(SK_Stroke *stk);
|
||||||
|
void sk_growStrokeBuffer(SK_Stroke *stk);
|
||||||
|
void sk_growStrokeBufferN(SK_Stroke *stk, int n);
|
||||||
|
|
||||||
|
void sk_replaceStrokePoint(SK_Stroke *stk, SK_Point *pt, int n);
|
||||||
|
void sk_insertStrokePoint(SK_Stroke *stk, SK_Point *pt, int n);
|
||||||
|
void sk_appendStrokePoint(SK_Stroke *stk, SK_Point *pt);
|
||||||
|
void sk_insertStrokePoints(SK_Stroke *stk, SK_Point *pts, int len, int start, int end);
|
||||||
|
|
||||||
|
void sk_trimStroke(SK_Stroke *stk, int start, int end);
|
||||||
|
void sk_straightenStroke(SK_Stroke *stk, int start, int end, float p_start[3], float p_end[3]);
|
||||||
|
void sk_polygonizeStroke(SK_Stroke *stk, int start, int end);
|
||||||
|
void sk_flattenStroke(SK_Stroke *stk, int start, int end);
|
||||||
|
void sk_reverseStroke(SK_Stroke *stk);
|
||||||
|
|
||||||
|
void sk_filterLastContinuousStroke(SK_Stroke *stk);
|
||||||
|
void sk_filterStroke(SK_Stroke *stk, int start, int end);
|
||||||
|
|
||||||
|
void sk_initPoint(SK_Point *pt, SK_DrawData *dd, float *no);
|
||||||
|
void sk_copyPoint(SK_Point *dst, SK_Point *src);
|
||||||
|
|
||||||
|
int sk_stroke_filtermval(SK_DrawData *dd);
|
||||||
|
void sk_endContinuousStroke(SK_Stroke *stk);
|
||||||
|
|
||||||
|
void sk_updateNextPoint(SK_Sketch *sketch, SK_Stroke *stk);
|
||||||
|
|
||||||
|
void sk_initDrawData(SK_DrawData *dd, short mval[2]);
|
||||||
|
|
||||||
|
void sk_deleteSelectedStrokes(SK_Sketch *sketch);
|
||||||
|
void sk_selectAllSketch(SK_Sketch *sketch, int mode);
|
||||||
|
|
||||||
|
#endif
|
@ -66,8 +66,7 @@
|
|||||||
#include "BKE_object.h"
|
#include "BKE_object.h"
|
||||||
#include "BKE_object.h"
|
#include "BKE_object.h"
|
||||||
#include "BKE_utildefines.h"
|
#include "BKE_utildefines.h"
|
||||||
|
#include "BKE_sketch.h"
|
||||||
//XXX #include "BIF_editdeform.h"
|
|
||||||
|
|
||||||
#include "IK_solver.h"
|
#include "IK_solver.h"
|
||||||
|
|
||||||
@ -144,7 +143,7 @@ void free_armature(bArmature *arm)
|
|||||||
|
|
||||||
/* free sketch */
|
/* free sketch */
|
||||||
if (arm->sketch) {
|
if (arm->sketch) {
|
||||||
ED_freeSketch(arm->sketch);
|
freeSketch(arm->sketch);
|
||||||
arm->sketch = NULL;
|
arm->sketch = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
600
source/blender/blenkernel/intern/sketch.c
Normal file
600
source/blender/blenkernel/intern/sketch.c
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
||||||
|
*
|
||||||
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
* Contributor(s): none yet.
|
||||||
|
*
|
||||||
|
* ***** END GPL LICENSE BLOCK *****
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
|
#include "MEM_guardedalloc.h"
|
||||||
|
|
||||||
|
#include "BLI_blenlib.h"
|
||||||
|
#include "BLI_arithb.h"
|
||||||
|
|
||||||
|
#include "BKE_sketch.h"
|
||||||
|
#include "BKE_utildefines.h"
|
||||||
|
|
||||||
|
#include "DNA_userdef_types.h"
|
||||||
|
|
||||||
|
void freeSketch(SK_Sketch *sketch)
|
||||||
|
{
|
||||||
|
SK_Stroke *stk, *next;
|
||||||
|
|
||||||
|
for (stk = sketch->strokes.first; stk; stk = next)
|
||||||
|
{
|
||||||
|
next = stk->next;
|
||||||
|
|
||||||
|
sk_freeStroke(stk);
|
||||||
|
}
|
||||||
|
|
||||||
|
BLI_freelistN(&sketch->depth_peels);
|
||||||
|
|
||||||
|
MEM_freeN(sketch);
|
||||||
|
}
|
||||||
|
|
||||||
|
SK_Sketch* createSketch()
|
||||||
|
{
|
||||||
|
SK_Sketch *sketch;
|
||||||
|
|
||||||
|
sketch = MEM_callocN(sizeof(SK_Sketch), "SK_Sketch");
|
||||||
|
|
||||||
|
sketch->active_stroke = NULL;
|
||||||
|
sketch->gesture = NULL;
|
||||||
|
|
||||||
|
sketch->strokes.first = NULL;
|
||||||
|
sketch->strokes.last = NULL;
|
||||||
|
|
||||||
|
return sketch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_initPoint(SK_Point *pt, SK_DrawData *dd, float *no)
|
||||||
|
{
|
||||||
|
if (no)
|
||||||
|
{
|
||||||
|
VECCOPY(pt->no, no);
|
||||||
|
Normalize(pt->no);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pt->no[0] = 0;
|
||||||
|
pt->no[1] = 0;
|
||||||
|
pt->no[2] = 1;
|
||||||
|
}
|
||||||
|
pt->p2d[0] = dd->mval[0];
|
||||||
|
pt->p2d[1] = dd->mval[1];
|
||||||
|
/* more init code here */
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_copyPoint(SK_Point *dst, SK_Point *src)
|
||||||
|
{
|
||||||
|
memcpy(dst, src, sizeof(SK_Point));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_allocStrokeBuffer(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
stk->points = MEM_callocN(sizeof(SK_Point) * stk->buf_size, "SK_Point buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_freeStroke(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
MEM_freeN(stk->points);
|
||||||
|
MEM_freeN(stk);
|
||||||
|
}
|
||||||
|
|
||||||
|
SK_Stroke* sk_createStroke()
|
||||||
|
{
|
||||||
|
SK_Stroke *stk;
|
||||||
|
|
||||||
|
stk = MEM_callocN(sizeof(SK_Stroke), "SK_Stroke");
|
||||||
|
|
||||||
|
stk->selected = 0;
|
||||||
|
stk->nb_points = 0;
|
||||||
|
stk->buf_size = SK_Stroke_BUFFER_INIT_SIZE;
|
||||||
|
|
||||||
|
sk_allocStrokeBuffer(stk);
|
||||||
|
|
||||||
|
return stk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_shrinkStrokeBuffer(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
if (stk->nb_points < stk->buf_size)
|
||||||
|
{
|
||||||
|
SK_Point *old_points = stk->points;
|
||||||
|
|
||||||
|
stk->buf_size = stk->nb_points;
|
||||||
|
|
||||||
|
sk_allocStrokeBuffer(stk);
|
||||||
|
|
||||||
|
memcpy(stk->points, old_points, sizeof(SK_Point) * stk->nb_points);
|
||||||
|
|
||||||
|
MEM_freeN(old_points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_growStrokeBuffer(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
if (stk->nb_points == stk->buf_size)
|
||||||
|
{
|
||||||
|
SK_Point *old_points = stk->points;
|
||||||
|
|
||||||
|
stk->buf_size *= 2;
|
||||||
|
|
||||||
|
sk_allocStrokeBuffer(stk);
|
||||||
|
|
||||||
|
memcpy(stk->points, old_points, sizeof(SK_Point) * stk->nb_points);
|
||||||
|
|
||||||
|
MEM_freeN(old_points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_growStrokeBufferN(SK_Stroke *stk, int n)
|
||||||
|
{
|
||||||
|
if (stk->nb_points + n > stk->buf_size)
|
||||||
|
{
|
||||||
|
SK_Point *old_points = stk->points;
|
||||||
|
|
||||||
|
while (stk->nb_points + n > stk->buf_size)
|
||||||
|
{
|
||||||
|
stk->buf_size *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
sk_allocStrokeBuffer(stk);
|
||||||
|
|
||||||
|
memcpy(stk->points, old_points, sizeof(SK_Point) * stk->nb_points);
|
||||||
|
|
||||||
|
MEM_freeN(old_points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sk_replaceStrokePoint(SK_Stroke *stk, SK_Point *pt, int n)
|
||||||
|
{
|
||||||
|
memcpy(stk->points + n, pt, sizeof(SK_Point));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_insertStrokePoint(SK_Stroke *stk, SK_Point *pt, int n)
|
||||||
|
{
|
||||||
|
int size = stk->nb_points - n;
|
||||||
|
|
||||||
|
sk_growStrokeBuffer(stk);
|
||||||
|
|
||||||
|
memmove(stk->points + n + 1, stk->points + n, size * sizeof(SK_Point));
|
||||||
|
|
||||||
|
memcpy(stk->points + n, pt, sizeof(SK_Point));
|
||||||
|
|
||||||
|
stk->nb_points++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_appendStrokePoint(SK_Stroke *stk, SK_Point *pt)
|
||||||
|
{
|
||||||
|
sk_growStrokeBuffer(stk);
|
||||||
|
|
||||||
|
memcpy(stk->points + stk->nb_points, pt, sizeof(SK_Point));
|
||||||
|
|
||||||
|
stk->nb_points++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_insertStrokePoints(SK_Stroke *stk, SK_Point *pts, int len, int start, int end)
|
||||||
|
{
|
||||||
|
int size = end - start + 1;
|
||||||
|
|
||||||
|
sk_growStrokeBufferN(stk, len - size);
|
||||||
|
|
||||||
|
if (len != size)
|
||||||
|
{
|
||||||
|
int tail_size = stk->nb_points - end + 1;
|
||||||
|
|
||||||
|
memmove(stk->points + start + len, stk->points + end + 1, tail_size * sizeof(SK_Point));
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(stk->points + start, pts, len * sizeof(SK_Point));
|
||||||
|
|
||||||
|
stk->nb_points += len - size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_trimStroke(SK_Stroke *stk, int start, int end)
|
||||||
|
{
|
||||||
|
int size = end - start + 1;
|
||||||
|
|
||||||
|
if (start > 0)
|
||||||
|
{
|
||||||
|
memmove(stk->points, stk->points + start, size * sizeof(SK_Point));
|
||||||
|
}
|
||||||
|
|
||||||
|
stk->nb_points = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_straightenStroke(SK_Stroke *stk, int start, int end, float p_start[3], float p_end[3])
|
||||||
|
{
|
||||||
|
SK_Point pt1, pt2;
|
||||||
|
SK_Point *prev, *next;
|
||||||
|
float delta_p[3];
|
||||||
|
int i, total;
|
||||||
|
|
||||||
|
total = end - start;
|
||||||
|
|
||||||
|
VecSubf(delta_p, p_end, p_start);
|
||||||
|
|
||||||
|
prev = stk->points + start;
|
||||||
|
next = stk->points + end;
|
||||||
|
|
||||||
|
VECCOPY(pt1.p, p_start);
|
||||||
|
VECCOPY(pt1.no, prev->no);
|
||||||
|
pt1.mode = prev->mode;
|
||||||
|
pt1.type = prev->type;
|
||||||
|
|
||||||
|
VECCOPY(pt2.p, p_end);
|
||||||
|
VECCOPY(pt2.no, next->no);
|
||||||
|
pt2.mode = next->mode;
|
||||||
|
pt2.type = next->type;
|
||||||
|
|
||||||
|
sk_insertStrokePoint(stk, &pt1, start + 1); /* insert after start */
|
||||||
|
sk_insertStrokePoint(stk, &pt2, end + 1); /* insert before end (since end was pushed back already) */
|
||||||
|
|
||||||
|
for (i = 1; i < total; i++)
|
||||||
|
{
|
||||||
|
float delta = (float)i / (float)total;
|
||||||
|
float *p = stk->points[start + 1 + i].p;
|
||||||
|
|
||||||
|
VECCOPY(p, delta_p);
|
||||||
|
VecMulf(p, delta);
|
||||||
|
VecAddf(p, p, p_start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_polygonizeStroke(SK_Stroke *stk, int start, int end)
|
||||||
|
{
|
||||||
|
int offset;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* find first exact points outside of range */
|
||||||
|
for (;start > 0; start--)
|
||||||
|
{
|
||||||
|
if (stk->points[start].type == PT_EXACT)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;end < stk->nb_points - 1; end++)
|
||||||
|
{
|
||||||
|
if (stk->points[end].type == PT_EXACT)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = start + 1;
|
||||||
|
|
||||||
|
for (i = start + 1; i < end; i++)
|
||||||
|
{
|
||||||
|
if (stk->points[i].type == PT_EXACT)
|
||||||
|
{
|
||||||
|
if (offset != i)
|
||||||
|
{
|
||||||
|
memcpy(stk->points + offset, stk->points + i, sizeof(SK_Point));
|
||||||
|
}
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* some points were removes, move end of array */
|
||||||
|
if (offset < end)
|
||||||
|
{
|
||||||
|
int size = stk->nb_points - end;
|
||||||
|
memmove(stk->points + offset, stk->points + end, size * sizeof(SK_Point));
|
||||||
|
stk->nb_points = offset + size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_flattenStroke(SK_Stroke *stk, int start, int end)
|
||||||
|
{
|
||||||
|
float normal[3], distance[3];
|
||||||
|
float limit;
|
||||||
|
int i, total;
|
||||||
|
|
||||||
|
total = end - start + 1;
|
||||||
|
|
||||||
|
VECCOPY(normal, stk->points[start].no);
|
||||||
|
|
||||||
|
VecSubf(distance, stk->points[end].p, stk->points[start].p);
|
||||||
|
Projf(normal, distance, normal);
|
||||||
|
limit = Normalize(normal);
|
||||||
|
|
||||||
|
for (i = 1; i < total - 1; i++)
|
||||||
|
{
|
||||||
|
float d = limit * i / total;
|
||||||
|
float offset[3];
|
||||||
|
float *p = stk->points[start + i].p;
|
||||||
|
|
||||||
|
VecSubf(distance, p, stk->points[start].p);
|
||||||
|
Projf(distance, distance, normal);
|
||||||
|
|
||||||
|
VECCOPY(offset, normal);
|
||||||
|
VecMulf(offset, d);
|
||||||
|
|
||||||
|
VecSubf(p, p, distance);
|
||||||
|
VecAddf(p, p, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_removeStroke(SK_Sketch *sketch, SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
if (sketch->active_stroke == stk)
|
||||||
|
{
|
||||||
|
sketch->active_stroke = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
BLI_remlink(&sketch->strokes, stk);
|
||||||
|
sk_freeStroke(stk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_reverseStroke(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
SK_Point *old_points = stk->points;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
sk_allocStrokeBuffer(stk);
|
||||||
|
|
||||||
|
for (i = 0; i < stk->nb_points; i++)
|
||||||
|
{
|
||||||
|
sk_copyPoint(stk->points + i, old_points + stk->nb_points - 1 - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_freeN(old_points);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Ramer-Douglas-Peucker algorithm for line simplification */
|
||||||
|
void sk_filterStroke(SK_Stroke *stk, int start, int end)
|
||||||
|
{
|
||||||
|
SK_Point *old_points = stk->points;
|
||||||
|
int nb_points = stk->nb_points;
|
||||||
|
char *marked = NULL;
|
||||||
|
char work;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (start == -1)
|
||||||
|
{
|
||||||
|
start = 0;
|
||||||
|
end = stk->nb_points - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sk_allocStrokeBuffer(stk);
|
||||||
|
stk->nb_points = 0;
|
||||||
|
|
||||||
|
/* adding points before range */
|
||||||
|
for (i = 0; i < start; i++)
|
||||||
|
{
|
||||||
|
sk_appendStrokePoint(stk, old_points + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
marked = MEM_callocN(nb_points, "marked array");
|
||||||
|
marked[start] = 1;
|
||||||
|
marked[end] = 1;
|
||||||
|
|
||||||
|
work = 1;
|
||||||
|
|
||||||
|
/* while still reducing */
|
||||||
|
while (work)
|
||||||
|
{
|
||||||
|
int ls, le;
|
||||||
|
work = 0;
|
||||||
|
|
||||||
|
ls = start;
|
||||||
|
le = start+1;
|
||||||
|
|
||||||
|
/* while not over interval */
|
||||||
|
while (ls < end)
|
||||||
|
{
|
||||||
|
int max_i = 0;
|
||||||
|
short v1[2];
|
||||||
|
float max_dist = 16; /* more than 4 pixels */
|
||||||
|
|
||||||
|
/* find the next marked point */
|
||||||
|
while(marked[le] == 0)
|
||||||
|
{
|
||||||
|
le++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* perpendicular vector to ls-le */
|
||||||
|
v1[1] = old_points[le].p2d[0] - old_points[ls].p2d[0];
|
||||||
|
v1[0] = old_points[ls].p2d[1] - old_points[le].p2d[1];
|
||||||
|
|
||||||
|
|
||||||
|
for( i = ls + 1; i < le; i++ )
|
||||||
|
{
|
||||||
|
float mul;
|
||||||
|
float dist;
|
||||||
|
short v2[2];
|
||||||
|
|
||||||
|
v2[0] = old_points[i].p2d[0] - old_points[ls].p2d[0];
|
||||||
|
v2[1] = old_points[i].p2d[1] - old_points[ls].p2d[1];
|
||||||
|
|
||||||
|
if (v2[0] == 0 && v2[1] == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mul = (float)(v1[0]*v2[0] + v1[1]*v2[1]) / (float)(v2[0]*v2[0] + v2[1]*v2[1]);
|
||||||
|
|
||||||
|
dist = mul * mul * (v2[0]*v2[0] + v2[1]*v2[1]);
|
||||||
|
|
||||||
|
if (dist > max_dist)
|
||||||
|
{
|
||||||
|
max_dist = dist;
|
||||||
|
max_i = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_i != 0)
|
||||||
|
{
|
||||||
|
work = 1;
|
||||||
|
marked[max_i] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ls = le;
|
||||||
|
le = ls + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* adding points after range */
|
||||||
|
for (i = start; i <= end; i++)
|
||||||
|
{
|
||||||
|
if (marked[i])
|
||||||
|
{
|
||||||
|
sk_appendStrokePoint(stk, old_points + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_freeN(marked);
|
||||||
|
|
||||||
|
/* adding points after range */
|
||||||
|
for (i = end + 1; i < nb_points; i++)
|
||||||
|
{
|
||||||
|
sk_appendStrokePoint(stk, old_points + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
MEM_freeN(old_points);
|
||||||
|
|
||||||
|
sk_shrinkStrokeBuffer(stk);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sk_filterLastContinuousStroke(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
int start, end;
|
||||||
|
|
||||||
|
end = stk->nb_points -1;
|
||||||
|
|
||||||
|
for (start = end - 1; start > 0 && stk->points[start].type == PT_CONTINUOUS; start--)
|
||||||
|
{
|
||||||
|
/* nothing to do here*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end - start > 1)
|
||||||
|
{
|
||||||
|
sk_filterStroke(stk, start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SK_Point *sk_lastStrokePoint(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
SK_Point *pt = NULL;
|
||||||
|
|
||||||
|
if (stk->nb_points > 0)
|
||||||
|
{
|
||||||
|
pt = stk->points + (stk->nb_points - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_endContinuousStroke(SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
stk->points[stk->nb_points - 1].type = PT_EXACT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_updateNextPoint(SK_Sketch *sketch, SK_Stroke *stk)
|
||||||
|
{
|
||||||
|
if (stk)
|
||||||
|
{
|
||||||
|
memcpy(&sketch->next_point, stk->points[stk->nb_points - 1].p, sizeof(SK_Point));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int sk_stroke_filtermval(SK_DrawData *dd)
|
||||||
|
{
|
||||||
|
int retval = 0;
|
||||||
|
if (ABS(dd->mval[0] - dd->previous_mval[0]) + ABS(dd->mval[1] - dd->previous_mval[1]) > U.gp_manhattendist)
|
||||||
|
{
|
||||||
|
retval = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_initDrawData(SK_DrawData *dd, short mval[2])
|
||||||
|
{
|
||||||
|
dd->mval[0] = mval[0];
|
||||||
|
dd->mval[1] = mval[1];
|
||||||
|
dd->previous_mval[0] = -1;
|
||||||
|
dd->previous_mval[1] = -1;
|
||||||
|
dd->type = PT_EXACT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sk_deleteSelectedStrokes(SK_Sketch *sketch)
|
||||||
|
{
|
||||||
|
SK_Stroke *stk, *next;
|
||||||
|
|
||||||
|
for (stk = sketch->strokes.first; stk; stk = next)
|
||||||
|
{
|
||||||
|
next = stk->next;
|
||||||
|
|
||||||
|
if (stk->selected == 1)
|
||||||
|
{
|
||||||
|
sk_removeStroke(sketch, stk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sk_selectAllSketch(SK_Sketch *sketch, int mode)
|
||||||
|
{
|
||||||
|
SK_Stroke *stk = NULL;
|
||||||
|
|
||||||
|
if (mode == -1)
|
||||||
|
{
|
||||||
|
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
||||||
|
{
|
||||||
|
stk->selected = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == 0)
|
||||||
|
{
|
||||||
|
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
||||||
|
{
|
||||||
|
stk->selected = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == 1)
|
||||||
|
{
|
||||||
|
int selected = 1;
|
||||||
|
|
||||||
|
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
||||||
|
{
|
||||||
|
selected &= stk->selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
selected ^= 1;
|
||||||
|
|
||||||
|
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
||||||
|
{
|
||||||
|
stk->selected = selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,7 @@
|
|||||||
#include "BKE_object.h"
|
#include "BKE_object.h"
|
||||||
#include "BKE_anim.h"
|
#include "BKE_anim.h"
|
||||||
#include "BKE_context.h"
|
#include "BKE_context.h"
|
||||||
|
#include "BKE_sketch.h"
|
||||||
|
|
||||||
#include "ED_view3d.h"
|
#include "ED_view3d.h"
|
||||||
#include "ED_screen.h"
|
#include "ED_screen.h"
|
||||||
@ -74,76 +75,21 @@
|
|||||||
//#include "mydevice.h"
|
//#include "mydevice.h"
|
||||||
#include "reeb.h"
|
#include "reeb.h"
|
||||||
|
|
||||||
typedef enum SK_PType
|
|
||||||
{
|
|
||||||
PT_CONTINUOUS,
|
|
||||||
PT_EXACT,
|
|
||||||
} SK_PType;
|
|
||||||
|
|
||||||
typedef enum SK_PMode
|
|
||||||
{
|
|
||||||
PT_SNAP,
|
|
||||||
PT_PROJECT,
|
|
||||||
} SK_PMode;
|
|
||||||
|
|
||||||
typedef struct SK_Point
|
typedef int (*GestureDetectFct)(bContext*, SK_Gesture*, SK_Sketch *);
|
||||||
{
|
typedef void (*GestureApplyFct)(bContext*, SK_Gesture*, SK_Sketch *);
|
||||||
float p[3];
|
|
||||||
short p2d[2];
|
|
||||||
float no[3];
|
|
||||||
float size;
|
|
||||||
SK_PType type;
|
|
||||||
SK_PMode mode;
|
|
||||||
} SK_Point;
|
|
||||||
|
|
||||||
typedef struct SK_Stroke
|
typedef struct SK_GestureAction {
|
||||||
{
|
char name[64];
|
||||||
struct SK_Stroke *next, *prev;
|
GestureDetectFct detect;
|
||||||
|
GestureApplyFct apply;
|
||||||
|
} SK_GestureAction;
|
||||||
|
|
||||||
SK_Point *points;
|
SK_Point boneSnap;
|
||||||
int nb_points;
|
int LAST_SNAP_POINT_VALID = 0;
|
||||||
int buf_size;
|
float LAST_SNAP_POINT[3];
|
||||||
int selected;
|
|
||||||
} SK_Stroke;
|
|
||||||
|
|
||||||
#define SK_OVERDRAW_LIMIT 5
|
|
||||||
|
|
||||||
typedef struct SK_Overdraw
|
|
||||||
{
|
|
||||||
SK_Stroke *target;
|
|
||||||
int start, end;
|
|
||||||
int count;
|
|
||||||
} SK_Overdraw;
|
|
||||||
|
|
||||||
#define SK_Stroke_BUFFER_INIT_SIZE 20
|
|
||||||
|
|
||||||
typedef struct SK_DrawData
|
|
||||||
{
|
|
||||||
short mval[2];
|
|
||||||
short previous_mval[2];
|
|
||||||
SK_PType type;
|
|
||||||
} SK_DrawData;
|
|
||||||
|
|
||||||
typedef struct SK_Intersection
|
|
||||||
{
|
|
||||||
struct SK_Intersection *next, *prev;
|
|
||||||
SK_Stroke *stroke;
|
|
||||||
int before;
|
|
||||||
int after;
|
|
||||||
int gesture_index;
|
|
||||||
float p[3];
|
|
||||||
float lambda; /* used for sorting intersection points */
|
|
||||||
} SK_Intersection;
|
|
||||||
|
|
||||||
typedef struct SK_Sketch
|
|
||||||
{
|
|
||||||
ListBase strokes;
|
|
||||||
ListBase depth_peels;
|
|
||||||
SK_Stroke *active_stroke;
|
|
||||||
SK_Stroke *gesture;
|
|
||||||
SK_Point next_point;
|
|
||||||
SK_Overdraw over;
|
|
||||||
} SK_Sketch;
|
|
||||||
|
|
||||||
typedef struct SK_StrokeIterator {
|
typedef struct SK_StrokeIterator {
|
||||||
HeadFct head;
|
HeadFct head;
|
||||||
@ -166,40 +112,12 @@ typedef struct SK_StrokeIterator {
|
|||||||
int stride;
|
int stride;
|
||||||
} SK_StrokeIterator;
|
} SK_StrokeIterator;
|
||||||
|
|
||||||
typedef struct SK_Gesture {
|
|
||||||
SK_Stroke *stk;
|
|
||||||
SK_Stroke *segments;
|
|
||||||
|
|
||||||
ListBase intersections;
|
|
||||||
ListBase self_intersections;
|
|
||||||
|
|
||||||
int nb_self_intersections;
|
|
||||||
int nb_intersections;
|
|
||||||
int nb_segments;
|
|
||||||
} SK_Gesture;
|
|
||||||
|
|
||||||
typedef int (*GestureDetectFct)(bContext*, SK_Gesture*, SK_Sketch *);
|
|
||||||
typedef void (*GestureApplyFct)(bContext*, SK_Gesture*, SK_Sketch *);
|
|
||||||
|
|
||||||
typedef struct SK_GestureAction {
|
|
||||||
char name[64];
|
|
||||||
GestureDetectFct detect;
|
|
||||||
GestureApplyFct apply;
|
|
||||||
} SK_GestureAction;
|
|
||||||
|
|
||||||
SK_Point boneSnap;
|
|
||||||
int LAST_SNAP_POINT_VALID = 0;
|
|
||||||
float LAST_SNAP_POINT[3];
|
|
||||||
|
|
||||||
/******************** PROTOTYPES ******************************/
|
/******************** PROTOTYPES ******************************/
|
||||||
|
|
||||||
void initStrokeIterator(BArcIterator *iter, SK_Stroke *stk, int start, int end);
|
void initStrokeIterator(BArcIterator *iter, SK_Stroke *stk, int start, int end);
|
||||||
|
|
||||||
void sk_deleteSelectedStrokes(SK_Sketch *sketch);
|
void sk_deleteSelectedStrokes(SK_Sketch *sketch);
|
||||||
|
|
||||||
void sk_freeStroke(SK_Stroke *stk);
|
|
||||||
void sk_freeSketch(SK_Sketch *sketch);
|
|
||||||
|
|
||||||
SK_Point *sk_lastStrokePoint(SK_Stroke *stk);
|
SK_Point *sk_lastStrokePoint(SK_Stroke *stk);
|
||||||
|
|
||||||
int sk_detectCutGesture(bContext *C, SK_Gesture *gest, SK_Sketch *sketch);
|
int sk_detectCutGesture(bContext *C, SK_Gesture *gest, SK_Sketch *sketch);
|
||||||
@ -561,331 +479,6 @@ void sk_retargetStroke(bContext *C, SK_Stroke *stk)
|
|||||||
|
|
||||||
/**************************************************************/
|
/**************************************************************/
|
||||||
|
|
||||||
void sk_freeSketch(SK_Sketch *sketch)
|
|
||||||
{
|
|
||||||
SK_Stroke *stk, *next;
|
|
||||||
|
|
||||||
for (stk = sketch->strokes.first; stk; stk = next)
|
|
||||||
{
|
|
||||||
next = stk->next;
|
|
||||||
|
|
||||||
sk_freeStroke(stk);
|
|
||||||
}
|
|
||||||
|
|
||||||
BLI_freelistN(&sketch->depth_peels);
|
|
||||||
|
|
||||||
MEM_freeN(sketch);
|
|
||||||
}
|
|
||||||
|
|
||||||
SK_Sketch* sk_createSketch()
|
|
||||||
{
|
|
||||||
SK_Sketch *sketch;
|
|
||||||
|
|
||||||
sketch = MEM_callocN(sizeof(SK_Sketch), "SK_Sketch");
|
|
||||||
|
|
||||||
sketch->active_stroke = NULL;
|
|
||||||
sketch->gesture = NULL;
|
|
||||||
|
|
||||||
sketch->strokes.first = NULL;
|
|
||||||
sketch->strokes.last = NULL;
|
|
||||||
|
|
||||||
return sketch;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_initPoint(bContext *C, SK_Point *pt, SK_DrawData *dd)
|
|
||||||
{
|
|
||||||
ARegion *ar = CTX_wm_region(C);
|
|
||||||
RegionView3D *rv3d = ar->regiondata;
|
|
||||||
|
|
||||||
VECCOPY(pt->no, rv3d->viewinv[2]);
|
|
||||||
pt->p2d[0] = dd->mval[0];
|
|
||||||
pt->p2d[1] = dd->mval[1];
|
|
||||||
Normalize(pt->no);
|
|
||||||
/* more init code here */
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_copyPoint(SK_Point *dst, SK_Point *src)
|
|
||||||
{
|
|
||||||
memcpy(dst, src, sizeof(SK_Point));
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_allocStrokeBuffer(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
stk->points = MEM_callocN(sizeof(SK_Point) * stk->buf_size, "SK_Point buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_freeStroke(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
MEM_freeN(stk->points);
|
|
||||||
MEM_freeN(stk);
|
|
||||||
}
|
|
||||||
|
|
||||||
SK_Stroke* sk_createStroke()
|
|
||||||
{
|
|
||||||
SK_Stroke *stk;
|
|
||||||
|
|
||||||
stk = MEM_callocN(sizeof(SK_Stroke), "SK_Stroke");
|
|
||||||
|
|
||||||
stk->selected = 0;
|
|
||||||
stk->nb_points = 0;
|
|
||||||
stk->buf_size = SK_Stroke_BUFFER_INIT_SIZE;
|
|
||||||
|
|
||||||
sk_allocStrokeBuffer(stk);
|
|
||||||
|
|
||||||
return stk;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_shrinkStrokeBuffer(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
if (stk->nb_points < stk->buf_size)
|
|
||||||
{
|
|
||||||
SK_Point *old_points = stk->points;
|
|
||||||
|
|
||||||
stk->buf_size = stk->nb_points;
|
|
||||||
|
|
||||||
sk_allocStrokeBuffer(stk);
|
|
||||||
|
|
||||||
memcpy(stk->points, old_points, sizeof(SK_Point) * stk->nb_points);
|
|
||||||
|
|
||||||
MEM_freeN(old_points);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_growStrokeBuffer(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
if (stk->nb_points == stk->buf_size)
|
|
||||||
{
|
|
||||||
SK_Point *old_points = stk->points;
|
|
||||||
|
|
||||||
stk->buf_size *= 2;
|
|
||||||
|
|
||||||
sk_allocStrokeBuffer(stk);
|
|
||||||
|
|
||||||
memcpy(stk->points, old_points, sizeof(SK_Point) * stk->nb_points);
|
|
||||||
|
|
||||||
MEM_freeN(old_points);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_growStrokeBufferN(SK_Stroke *stk, int n)
|
|
||||||
{
|
|
||||||
if (stk->nb_points + n > stk->buf_size)
|
|
||||||
{
|
|
||||||
SK_Point *old_points = stk->points;
|
|
||||||
|
|
||||||
while (stk->nb_points + n > stk->buf_size)
|
|
||||||
{
|
|
||||||
stk->buf_size *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
sk_allocStrokeBuffer(stk);
|
|
||||||
|
|
||||||
memcpy(stk->points, old_points, sizeof(SK_Point) * stk->nb_points);
|
|
||||||
|
|
||||||
MEM_freeN(old_points);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void sk_replaceStrokePoint(SK_Stroke *stk, SK_Point *pt, int n)
|
|
||||||
{
|
|
||||||
memcpy(stk->points + n, pt, sizeof(SK_Point));
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_insertStrokePoint(SK_Stroke *stk, SK_Point *pt, int n)
|
|
||||||
{
|
|
||||||
int size = stk->nb_points - n;
|
|
||||||
|
|
||||||
sk_growStrokeBuffer(stk);
|
|
||||||
|
|
||||||
memmove(stk->points + n + 1, stk->points + n, size * sizeof(SK_Point));
|
|
||||||
|
|
||||||
memcpy(stk->points + n, pt, sizeof(SK_Point));
|
|
||||||
|
|
||||||
stk->nb_points++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_appendStrokePoint(SK_Stroke *stk, SK_Point *pt)
|
|
||||||
{
|
|
||||||
sk_growStrokeBuffer(stk);
|
|
||||||
|
|
||||||
memcpy(stk->points + stk->nb_points, pt, sizeof(SK_Point));
|
|
||||||
|
|
||||||
stk->nb_points++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_insertStrokePoints(SK_Stroke *stk, SK_Point *pts, int len, int start, int end)
|
|
||||||
{
|
|
||||||
int size = end - start + 1;
|
|
||||||
|
|
||||||
sk_growStrokeBufferN(stk, len - size);
|
|
||||||
|
|
||||||
if (len != size)
|
|
||||||
{
|
|
||||||
int tail_size = stk->nb_points - end + 1;
|
|
||||||
|
|
||||||
memmove(stk->points + start + len, stk->points + end + 1, tail_size * sizeof(SK_Point));
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(stk->points + start, pts, len * sizeof(SK_Point));
|
|
||||||
|
|
||||||
stk->nb_points += len - size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_trimStroke(SK_Stroke *stk, int start, int end)
|
|
||||||
{
|
|
||||||
int size = end - start + 1;
|
|
||||||
|
|
||||||
if (start > 0)
|
|
||||||
{
|
|
||||||
memmove(stk->points, stk->points + start, size * sizeof(SK_Point));
|
|
||||||
}
|
|
||||||
|
|
||||||
stk->nb_points = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_straightenStroke(SK_Stroke *stk, int start, int end, float p_start[3], float p_end[3])
|
|
||||||
{
|
|
||||||
SK_Point pt1, pt2;
|
|
||||||
SK_Point *prev, *next;
|
|
||||||
float delta_p[3];
|
|
||||||
int i, total;
|
|
||||||
|
|
||||||
total = end - start;
|
|
||||||
|
|
||||||
VecSubf(delta_p, p_end, p_start);
|
|
||||||
|
|
||||||
prev = stk->points + start;
|
|
||||||
next = stk->points + end;
|
|
||||||
|
|
||||||
VECCOPY(pt1.p, p_start);
|
|
||||||
VECCOPY(pt1.no, prev->no);
|
|
||||||
pt1.mode = prev->mode;
|
|
||||||
pt1.type = prev->type;
|
|
||||||
|
|
||||||
VECCOPY(pt2.p, p_end);
|
|
||||||
VECCOPY(pt2.no, next->no);
|
|
||||||
pt2.mode = next->mode;
|
|
||||||
pt2.type = next->type;
|
|
||||||
|
|
||||||
sk_insertStrokePoint(stk, &pt1, start + 1); /* insert after start */
|
|
||||||
sk_insertStrokePoint(stk, &pt2, end + 1); /* insert before end (since end was pushed back already) */
|
|
||||||
|
|
||||||
for (i = 1; i < total; i++)
|
|
||||||
{
|
|
||||||
float delta = (float)i / (float)total;
|
|
||||||
float *p = stk->points[start + 1 + i].p;
|
|
||||||
|
|
||||||
VECCOPY(p, delta_p);
|
|
||||||
VecMulf(p, delta);
|
|
||||||
VecAddf(p, p, p_start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_polygonizeStroke(SK_Stroke *stk, int start, int end)
|
|
||||||
{
|
|
||||||
int offset;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* find first exact points outside of range */
|
|
||||||
for (;start > 0; start--)
|
|
||||||
{
|
|
||||||
if (stk->points[start].type == PT_EXACT)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;end < stk->nb_points - 1; end++)
|
|
||||||
{
|
|
||||||
if (stk->points[end].type == PT_EXACT)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = start + 1;
|
|
||||||
|
|
||||||
for (i = start + 1; i < end; i++)
|
|
||||||
{
|
|
||||||
if (stk->points[i].type == PT_EXACT)
|
|
||||||
{
|
|
||||||
if (offset != i)
|
|
||||||
{
|
|
||||||
memcpy(stk->points + offset, stk->points + i, sizeof(SK_Point));
|
|
||||||
}
|
|
||||||
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* some points were removes, move end of array */
|
|
||||||
if (offset < end)
|
|
||||||
{
|
|
||||||
int size = stk->nb_points - end;
|
|
||||||
memmove(stk->points + offset, stk->points + end, size * sizeof(SK_Point));
|
|
||||||
stk->nb_points = offset + size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_flattenStroke(SK_Stroke *stk, int start, int end)
|
|
||||||
{
|
|
||||||
float normal[3], distance[3];
|
|
||||||
float limit;
|
|
||||||
int i, total;
|
|
||||||
|
|
||||||
total = end - start + 1;
|
|
||||||
|
|
||||||
VECCOPY(normal, stk->points[start].no);
|
|
||||||
|
|
||||||
VecSubf(distance, stk->points[end].p, stk->points[start].p);
|
|
||||||
Projf(normal, distance, normal);
|
|
||||||
limit = Normalize(normal);
|
|
||||||
|
|
||||||
for (i = 1; i < total - 1; i++)
|
|
||||||
{
|
|
||||||
float d = limit * i / total;
|
|
||||||
float offset[3];
|
|
||||||
float *p = stk->points[start + i].p;
|
|
||||||
|
|
||||||
VecSubf(distance, p, stk->points[start].p);
|
|
||||||
Projf(distance, distance, normal);
|
|
||||||
|
|
||||||
VECCOPY(offset, normal);
|
|
||||||
VecMulf(offset, d);
|
|
||||||
|
|
||||||
VecSubf(p, p, distance);
|
|
||||||
VecAddf(p, p, offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_removeStroke(SK_Sketch *sketch, SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
if (sketch->active_stroke == stk)
|
|
||||||
{
|
|
||||||
sketch->active_stroke = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
BLI_remlink(&sketch->strokes, stk);
|
|
||||||
sk_freeStroke(stk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_reverseStroke(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
SK_Point *old_points = stk->points;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
sk_allocStrokeBuffer(stk);
|
|
||||||
|
|
||||||
for (i = 0; i < stk->nb_points; i++)
|
|
||||||
{
|
|
||||||
sk_copyPoint(stk->points + i, old_points + stk->nb_points - 1 - i);
|
|
||||||
}
|
|
||||||
|
|
||||||
MEM_freeN(old_points);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void sk_cancelStroke(SK_Sketch *sketch)
|
void sk_cancelStroke(SK_Sketch *sketch)
|
||||||
{
|
{
|
||||||
if (sketch->active_stroke != NULL)
|
if (sketch->active_stroke != NULL)
|
||||||
@ -895,201 +488,6 @@ void sk_cancelStroke(SK_Sketch *sketch)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sk_filterStroke(SK_Stroke *stk, int start, int end)
|
|
||||||
{
|
|
||||||
SK_Point *old_points = stk->points;
|
|
||||||
int nb_points = stk->nb_points;
|
|
||||||
char *marked = NULL;
|
|
||||||
char work;
|
|
||||||
int i, j;
|
|
||||||
|
|
||||||
if (start == -1)
|
|
||||||
{
|
|
||||||
start = 0;
|
|
||||||
end = stk->nb_points - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sk_allocStrokeBuffer(stk);
|
|
||||||
stk->nb_points = 0;
|
|
||||||
|
|
||||||
/* adding points before range */
|
|
||||||
for (i = 0; i < start; i++)
|
|
||||||
{
|
|
||||||
sk_appendStrokePoint(stk, old_points + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
|
|
||||||
/* Apply reverse Chaikin filter to simplify the polyline
|
|
||||||
* */
|
|
||||||
for (i = start, j = start; i <= end; i++)
|
|
||||||
{
|
|
||||||
if (i - j == 3)
|
|
||||||
{
|
|
||||||
SK_Point pt;
|
|
||||||
float vec[3];
|
|
||||||
|
|
||||||
sk_copyPoint(&pt, &old_points[j+1]);
|
|
||||||
|
|
||||||
pt.p[0] = 0;
|
|
||||||
pt.p[1] = 0;
|
|
||||||
pt.p[2] = 0;
|
|
||||||
|
|
||||||
VECCOPY(vec, old_points[j].p);
|
|
||||||
VecMulf(vec, -0.25);
|
|
||||||
VecAddf(pt.p, pt.p, vec);
|
|
||||||
|
|
||||||
VECCOPY(vec, old_points[j+1].p);
|
|
||||||
VecMulf(vec, 0.75);
|
|
||||||
VecAddf(pt.p, pt.p, vec);
|
|
||||||
|
|
||||||
VECCOPY(vec, old_points[j+2].p);
|
|
||||||
VecMulf(vec, 0.75);
|
|
||||||
VecAddf(pt.p, pt.p, vec);
|
|
||||||
|
|
||||||
VECCOPY(vec, old_points[j+3].p);
|
|
||||||
VecMulf(vec, -0.25);
|
|
||||||
VecAddf(pt.p, pt.p, vec);
|
|
||||||
|
|
||||||
pt.size = -0.25 * old_points[j].size + 0.75 * old_points[j+1].size + 0.75 * old_points[j+2].size - 0.25 * old_points[j+3].size;
|
|
||||||
|
|
||||||
sk_appendStrokePoint(stk, &pt);
|
|
||||||
|
|
||||||
j += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this might be uneeded when filtering last continuous stroke */
|
|
||||||
if (old_points[i].type == PT_EXACT)
|
|
||||||
{
|
|
||||||
sk_appendStrokePoint(stk, old_points + i);
|
|
||||||
j = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
/* Ramer-Douglas-Peucker */
|
|
||||||
marked = MEM_callocN(nb_points, "marked array");
|
|
||||||
marked[start] = 1;
|
|
||||||
marked[end] = 1;
|
|
||||||
|
|
||||||
work = 1;
|
|
||||||
|
|
||||||
/* while still reducing */
|
|
||||||
while (work)
|
|
||||||
{
|
|
||||||
int ls, le;
|
|
||||||
work = 0;
|
|
||||||
|
|
||||||
ls = start;
|
|
||||||
le = start+1;
|
|
||||||
|
|
||||||
/* while not over interval */
|
|
||||||
while (ls < end)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
int max_i = 0;
|
|
||||||
short v1[2];
|
|
||||||
float max_dist = 16; /* more than 4 pixels */
|
|
||||||
|
|
||||||
/* find the next marked point */
|
|
||||||
while(marked[le] == 0)
|
|
||||||
{
|
|
||||||
le++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* perpendicular vector to ls-le */
|
|
||||||
v1[1] = old_points[le].p2d[0] - old_points[ls].p2d[0];
|
|
||||||
v1[0] = old_points[ls].p2d[1] - old_points[le].p2d[1];
|
|
||||||
|
|
||||||
|
|
||||||
for( i = ls + 1; i < le; i++ )
|
|
||||||
{
|
|
||||||
float mul;
|
|
||||||
float dist;
|
|
||||||
short v2[2];
|
|
||||||
|
|
||||||
v2[0] = old_points[i].p2d[0] - old_points[ls].p2d[0];
|
|
||||||
v2[1] = old_points[i].p2d[1] - old_points[ls].p2d[1];
|
|
||||||
|
|
||||||
if (v2[0] == 0 && v2[1] == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
mul = (float)(v1[0]*v2[0] + v1[1]*v2[1]) / (float)(v2[0]*v2[0] + v2[1]*v2[1]);
|
|
||||||
|
|
||||||
dist = mul * mul * (v2[0]*v2[0] + v2[1]*v2[1]);
|
|
||||||
|
|
||||||
if (dist > max_dist)
|
|
||||||
{
|
|
||||||
max_dist = dist;
|
|
||||||
max_i = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max_i != 0)
|
|
||||||
{
|
|
||||||
work = 1;
|
|
||||||
marked[max_i] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ls = le;
|
|
||||||
le = ls + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* adding points after range */
|
|
||||||
for (i = start; i <= end; i++)
|
|
||||||
{
|
|
||||||
if (marked[i])
|
|
||||||
{
|
|
||||||
sk_appendStrokePoint(stk, old_points + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MEM_freeN(marked);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* adding points after range */
|
|
||||||
for (i = end + 1; i < nb_points; i++)
|
|
||||||
{
|
|
||||||
sk_appendStrokePoint(stk, old_points + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
MEM_freeN(old_points);
|
|
||||||
|
|
||||||
sk_shrinkStrokeBuffer(stk);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_filterLastContinuousStroke(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
int start, end;
|
|
||||||
|
|
||||||
end = stk->nb_points -1;
|
|
||||||
|
|
||||||
for (start = end - 1; start > 0 && stk->points[start].type == PT_CONTINUOUS; start--)
|
|
||||||
{
|
|
||||||
/* nothing to do here*/
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end - start > 1)
|
|
||||||
{
|
|
||||||
sk_filterStroke(stk, start, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SK_Point *sk_lastStrokePoint(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
SK_Point *pt = NULL;
|
|
||||||
|
|
||||||
if (stk->nb_points > 0)
|
|
||||||
{
|
|
||||||
pt = stk->points + (stk->nb_points - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
float sk_clampPointSize(SK_Point *pt, float size)
|
float sk_clampPointSize(SK_Point *pt, float size)
|
||||||
{
|
{
|
||||||
@ -1415,12 +813,6 @@ void sk_updateOverdraw(bContext *C, SK_Sketch *sketch, SK_Stroke *stk, SK_DrawDa
|
|||||||
int closest_index = -1;
|
int closest_index = -1;
|
||||||
int dist = SNAP_MIN_DISTANCE * 2;
|
int dist = SNAP_MIN_DISTANCE * 2;
|
||||||
|
|
||||||
// /* If snapping, don't start overdraw */ Can't do that, snap is embed too now
|
|
||||||
// if (sk_lastStrokePoint(stk)->mode == PT_SNAP)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
for (target = sketch->strokes.first; target; target = target->next)
|
for (target = sketch->strokes.first; target; target = target->next)
|
||||||
{
|
{
|
||||||
if (target != stk)
|
if (target != stk)
|
||||||
@ -1659,9 +1051,11 @@ int sk_getStrokeDrawPoint(bContext *C, SK_Point *pt, SK_Sketch *sketch, SK_Strok
|
|||||||
|
|
||||||
int sk_addStrokeDrawPoint(bContext *C, SK_Sketch *sketch, SK_Stroke *stk, SK_DrawData *dd)
|
int sk_addStrokeDrawPoint(bContext *C, SK_Sketch *sketch, SK_Stroke *stk, SK_DrawData *dd)
|
||||||
{
|
{
|
||||||
|
ARegion *ar = CTX_wm_region(C);
|
||||||
|
RegionView3D *rv3d = ar->regiondata;
|
||||||
SK_Point pt;
|
SK_Point pt;
|
||||||
|
|
||||||
sk_initPoint(C, &pt, dd);
|
sk_initPoint(&pt, dd, rv3d->viewinv[2]);
|
||||||
|
|
||||||
sk_getStrokeDrawPoint(C, &pt, sketch, stk, dd);
|
sk_getStrokeDrawPoint(C, &pt, sketch, stk, dd);
|
||||||
|
|
||||||
@ -1822,9 +1216,11 @@ int sk_getStrokeSnapPoint(bContext *C, SK_Point *pt, SK_Sketch *sketch, SK_Strok
|
|||||||
int sk_addStrokeSnapPoint(bContext *C, SK_Sketch *sketch, SK_Stroke *stk, SK_DrawData *dd)
|
int sk_addStrokeSnapPoint(bContext *C, SK_Sketch *sketch, SK_Stroke *stk, SK_DrawData *dd)
|
||||||
{
|
{
|
||||||
int point_added;
|
int point_added;
|
||||||
|
ARegion *ar = CTX_wm_region(C);
|
||||||
|
RegionView3D *rv3d = ar->regiondata;
|
||||||
SK_Point pt;
|
SK_Point pt;
|
||||||
|
|
||||||
sk_initPoint(C, &pt, dd);
|
sk_initPoint(&pt, dd, rv3d->viewinv[2]);
|
||||||
|
|
||||||
point_added = sk_getStrokeSnapPoint(C, &pt, sketch, stk, dd);
|
point_added = sk_getStrokeSnapPoint(C, &pt, sketch, stk, dd);
|
||||||
|
|
||||||
@ -1910,38 +1306,6 @@ void sk_getStrokePoint(bContext *C, SK_Point *pt, SK_Sketch *sketch, SK_Stroke *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sk_endContinuousStroke(SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
stk->points[stk->nb_points - 1].type = PT_EXACT;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_updateNextPoint(SK_Sketch *sketch, SK_Stroke *stk)
|
|
||||||
{
|
|
||||||
if (stk)
|
|
||||||
{
|
|
||||||
memcpy(&sketch->next_point, stk->points[stk->nb_points - 1].p, sizeof(SK_Point));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int sk_stroke_filtermval(SK_DrawData *dd)
|
|
||||||
{
|
|
||||||
int retval = 0;
|
|
||||||
if (ABS(dd->mval[0] - dd->previous_mval[0]) + ABS(dd->mval[1] - dd->previous_mval[1]) > U.gp_manhattendist)
|
|
||||||
{
|
|
||||||
retval = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_initDrawData(SK_DrawData *dd, short mval[2])
|
|
||||||
{
|
|
||||||
dd->mval[0] = mval[0];
|
|
||||||
dd->mval[1] = mval[1];
|
|
||||||
dd->previous_mval[0] = -1;
|
|
||||||
dd->previous_mval[1] = -1;
|
|
||||||
dd->type = PT_EXACT;
|
|
||||||
}
|
|
||||||
/********************************************/
|
/********************************************/
|
||||||
|
|
||||||
static void* headPoint(void *arg);
|
static void* headPoint(void *arg);
|
||||||
@ -2789,56 +2153,6 @@ void sk_applyGesture(bContext *C, SK_Sketch *sketch)
|
|||||||
|
|
||||||
/********************************************/
|
/********************************************/
|
||||||
|
|
||||||
void sk_deleteSelectedStrokes(SK_Sketch *sketch)
|
|
||||||
{
|
|
||||||
SK_Stroke *stk, *next;
|
|
||||||
|
|
||||||
for (stk = sketch->strokes.first; stk; stk = next)
|
|
||||||
{
|
|
||||||
next = stk->next;
|
|
||||||
|
|
||||||
if (stk->selected == 1)
|
|
||||||
{
|
|
||||||
sk_removeStroke(sketch, stk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_selectAllSketch(SK_Sketch *sketch, int mode)
|
|
||||||
{
|
|
||||||
SK_Stroke *stk = NULL;
|
|
||||||
|
|
||||||
if (mode == -1)
|
|
||||||
{
|
|
||||||
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
|
||||||
{
|
|
||||||
stk->selected = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (mode == 0)
|
|
||||||
{
|
|
||||||
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
|
||||||
{
|
|
||||||
stk->selected = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (mode == 1)
|
|
||||||
{
|
|
||||||
int selected = 1;
|
|
||||||
|
|
||||||
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
|
||||||
{
|
|
||||||
selected &= stk->selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
selected ^= 1;
|
|
||||||
|
|
||||||
for (stk = sketch->strokes.first; stk; stk = stk->next)
|
|
||||||
{
|
|
||||||
stk->selected = selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sk_selectStroke(bContext *C, SK_Sketch *sketch, short mval[2], int extend)
|
void sk_selectStroke(bContext *C, SK_Sketch *sketch, short mval[2], int extend)
|
||||||
{
|
{
|
||||||
@ -2987,7 +2301,7 @@ void sk_drawSketch(Scene *scene, View3D *v3d, SK_Sketch *sketch, int with_names)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 1
|
||||||
if (sketch->depth_peels.first != NULL)
|
if (sketch->depth_peels.first != NULL)
|
||||||
{
|
{
|
||||||
float colors[8][3] = {
|
float colors[8][3] = {
|
||||||
@ -3204,16 +2518,6 @@ void BIF_selectAllSketch(bContext *C, int mode)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void ED_freeSketch(SK_Sketch *sketch)
|
|
||||||
{
|
|
||||||
sk_freeSketch(sketch);
|
|
||||||
}
|
|
||||||
|
|
||||||
SK_Sketch* ED_createSketch()
|
|
||||||
{
|
|
||||||
return sk_createSketch();
|
|
||||||
}
|
|
||||||
|
|
||||||
SK_Sketch* contextSketch(const bContext *C, int create)
|
SK_Sketch* contextSketch(const bContext *C, int create)
|
||||||
{
|
{
|
||||||
Object *obedit = CTX_data_edit_object(C);
|
Object *obedit = CTX_data_edit_object(C);
|
||||||
@ -3225,7 +2529,7 @@ SK_Sketch* contextSketch(const bContext *C, int create)
|
|||||||
|
|
||||||
if (arm->sketch == NULL && create)
|
if (arm->sketch == NULL && create)
|
||||||
{
|
{
|
||||||
arm->sketch = sk_createSketch();
|
arm->sketch = createSketch();
|
||||||
}
|
}
|
||||||
sketch = arm->sketch;
|
sketch = arm->sketch;
|
||||||
}
|
}
|
||||||
@ -3244,7 +2548,7 @@ SK_Sketch* viewcontextSketch(ViewContext *vc, int create)
|
|||||||
|
|
||||||
if (arm->sketch == NULL && create)
|
if (arm->sketch == NULL && create)
|
||||||
{
|
{
|
||||||
arm->sketch = sk_createSketch();
|
arm->sketch = createSketch();
|
||||||
}
|
}
|
||||||
sketch = arm->sketch;
|
sketch = arm->sketch;
|
||||||
}
|
}
|
||||||
|
@ -141,9 +141,6 @@ int ED_operator_sketch_mode_active_stroke(struct bContext *C);
|
|||||||
int ED_operator_sketch_full_mode(struct bContext *C);
|
int ED_operator_sketch_full_mode(struct bContext *C);
|
||||||
int ED_operator_sketch_mode(const struct bContext *C);
|
int ED_operator_sketch_mode(const struct bContext *C);
|
||||||
|
|
||||||
void ED_freeSketch(struct SK_Sketch *sketch);
|
|
||||||
struct SK_Sketch* ED_createSketch();
|
|
||||||
|
|
||||||
void BIF_convertSketch(struct bContext *C);
|
void BIF_convertSketch(struct bContext *C);
|
||||||
void BIF_deleteSketch(struct bContext *C);
|
void BIF_deleteSketch(struct bContext *C);
|
||||||
void BIF_selectAllSketch(struct bContext *C, int mode); /* -1: deselect, 0: select, 1: toggle */
|
void BIF_selectAllSketch(struct bContext *C, int mode); /* -1: deselect, 0: select, 1: toggle */
|
||||||
|
Loading…
Reference in New Issue
Block a user