This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/sculpt_paint/sculpt.c
Joseph Eagar 67ff197cb1 Merge with trunk/2.5 at r25563
Most likely will not compile for others, I'd appreciate any build errors
and missing files reports (I can never seem to get everything committed
and all the build systems working without help).

Porting over the sculpt/multires tools was a breeze,
thanks goes to brecht for a design that didn't exclude
ngons and was easy to port.

Note that I've not tested externally-backed multires
file support yet.  Also, I still need to write version
patch code for some cases.

Some notes:

* Like trunk, topological changes don't update multires right,
  so e.g. subdivide will duplicate multires data on the new faces,
  instead of subdividing it.
* If you set the debug value (ctrl-alt-d) to 1 it'll turn on
  my experiments in speeding up sculpting on higher-res multires
  meshes (but note it makes partial redraw not completely accurate).
* There's a bug where you have to go through editmode to get out
  of sculpt mode, not sure if I inherited or created this myself.
2010-01-05 22:33:41 +00:00

2306 lines
61 KiB
C

/*
* $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.
*
* The Original Code is Copyright (C) 2006 by Nicholas Bishop
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): none yet.
*
* ***** END GPL LICENSE BLOCK *****
*
* Implements the Sculpt Mode tools
*
*/
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "BLI_blenlib.h"
#include "BLI_dynstr.h"
#include "BLI_ghash.h"
#include "BLI_pbvh.h"
#include "BLI_threads.h"
#include "DNA_armature_types.h"
#include "DNA_brush_types.h"
#include "DNA_image_types.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_scene_types.h"
#include "DNA_texture_types.h"
#include "DNA_view3d_types.h"
#include "DNA_userdef_types.h"
#include "DNA_color_types.h"
#include "BKE_brush.h"
#include "BKE_cdderivedmesh.h"
#include "BKE_context.h"
#include "BKE_customdata.h"
#include "BKE_DerivedMesh.h"
#include "BKE_depsgraph.h"
#include "BKE_global.h"
#include "BKE_image.h"
#include "BKE_key.h"
#include "BKE_library.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_multires.h"
#include "BKE_paint.h"
#include "BKE_report.h"
#include "BKE_texture.h"
#include "BKE_utildefines.h"
#include "BKE_colortools.h"
#include "BIF_gl.h"
#include "BIF_glutil.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_space_api.h"
#include "ED_util.h"
#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "IMB_imbuf_types.h"
#include "RE_render_ext.h"
#include "RE_shader_ext.h" /*for multitex_ext*/
#include "GPU_draw.h"
#include "gpu_buffers.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
/* Number of vertices to average in order to determine the flatten distance */
#define FLATTEN_SAMPLE_SIZE 10
/* ===== STRUCTS =====
*
*/
typedef enum StrokeFlags {
CLIP_X = 1,
CLIP_Y = 2,
CLIP_Z = 4
} StrokeFlags;
/* Cache stroke properties. Used because
RNA property lookup isn't particularly fast.
For descriptions of these settings, check the operator properties.
*/
typedef struct StrokeCache {
/* Invariants */
float initial_radius;
float scale[3];
int flag;
float clip_tolerance[3];
float initial_mouse[2];
/* Variants */
float radius;
float true_location[3];
float location[3];
float flip;
float pressure;
float mouse[2];
float bstrength;
float tex_mouse[2];
rctf prect;
/* The rest is temporary storage that isn't saved as a property */
int first_time; /* Beginning of stroke may do some things special */
bglMats *mats;
/* Clean this up! */
ViewContext *vc;
Brush *brush;
float (*face_norms)[3]; /* Copy of the mesh faces' normals */
float rotation; /* Texture rotation (radians) for anchored and rake modes */
int pixel_radius, previous_pixel_radius;
PBVHNode **grab_active_nodes[8]; /* The same list of nodes is used throught grab stroke */
int grab_active_totnode[8];
float grab_active_location[8][3];
float grab_delta[3], grab_delta_symmetry[3];
float old_grab_location[3], orig_grab_location[3];
int symmetry; /* Symmetry index between 0 and 7 */
float view_normal[3], view_normal_symmetry[3];
int last_rake[2]; /* Last location of updating rake rotation */
int original;
} StrokeCache;
/* ===== OPENGL =====
*
* Simple functions to get data from the GL
*/
/* Convert a point in model coordinates to 2D screen coordinates. */
static void projectf(bglMats *mats, const float v[3], float p[2])
{
double ux, uy, uz;
gluProject(v[0],v[1],v[2], mats->modelview, mats->projection,
(GLint *)mats->viewport, &ux, &uy, &uz);
p[0]= ux;
p[1]= uy;
}
/*XXX: static void project(bglMats *mats, const float v[3], short p[2])
{
float f[2];
projectf(mats, v, f);
p[0]= f[0];
p[1]= f[1];
}
*/
/*** BVH Tree ***/
/* Get a screen-space rectangle of the modified area */
int sculpt_get_redraw_rect(ARegion *ar, RegionView3D *rv3d,
Object *ob, rcti *rect)
{
StrokeCache *cache = ob->sculpt->cache;
float bb_min[3], bb_max[3], pmat[4][4];
int i, j, k;
if (G.rt == 1) {
rect->xmin = cache->prect.xmin;
rect->xmax = cache->prect.xmax;
rect->ymin = cache->prect.ymin;
rect->ymax = cache->prect.ymax;
return rect->xmin < rect->xmax && rect->ymin < rect->ymax;;
}
view3d_get_object_project_mat(rv3d, ob, pmat);
BLI_pbvh_redraw_BB(ob->sculpt->tree, bb_min, bb_max);
rect->xmin = rect->ymin = INT_MAX;
rect->xmax = rect->ymax = INT_MIN;
if(bb_min[0] > bb_max[0] || bb_min[1] > bb_max[1] || bb_min[2] > bb_max[2])
return 0;
for(i = 0; i < 2; ++i) {
for(j = 0; j < 2; ++j) {
for(k = 0; k < 2; ++k) {
float vec[3], proj[2];
vec[0] = i ? bb_min[0] : bb_max[0];
vec[1] = j ? bb_min[1] : bb_max[1];
vec[2] = k ? bb_min[2] : bb_max[2];
view3d_project_float(ar, vec, proj, pmat);
rect->xmin = MIN2(rect->xmin, proj[0]);
rect->xmax = MAX2(rect->xmax, proj[0]);
rect->ymin = MIN2(rect->ymin, proj[1]);
rect->ymax = MAX2(rect->ymax, proj[1]);
}
}
}
return rect->xmin < rect->xmax && rect->ymin < rect->ymax;
}
void sculpt_get_redraw_planes(float planes[4][4], ARegion *ar,
RegionView3D *rv3d, Object *ob)
{
BoundBox *bb = MEM_callocN(sizeof(BoundBox), "sculpt boundbox");
bglMats mats;
StrokeCache *cache = ob->sculpt->cache;
rcti rect;
int i;
view3d_get_transformation(ar, rv3d, ob, &mats);
sculpt_get_redraw_rect(ar, rv3d,ob, &rect);
#if 1
/* use some extra space just in case */
rect.xmin -= 2;
rect.xmax += 2;
rect.ymin -= 2;
rect.ymax += 2;
#else
/* it was doing this before, allows to redraw a smaller
part of the screen but also gives artifaces .. */
rect.xmin += 2;
rect.xmax -= 2;
rect.ymin += 2;
rect.ymax -= 2;
#endif
view3d_calculate_clipping(bb, planes, &mats, &rect);
for(i = 0; i < 16; ++i)
((float*)planes)[i] = -((float*)planes)[i];
MEM_freeN(bb);
/* clear redraw flag from nodes */
BLI_pbvh_update(ob->sculpt->tree, PBVH_UpdateRedraw, NULL);
/*clear prect*/
cache->prect.xmin = FLT_MAX;
cache->prect.xmax = -FLT_MAX;
cache->prect.ymin = FLT_MAX;
cache->prect.ymax = -FLT_MAX;
}
/************************** Undo *************************/
typedef struct SculptUndoNode {
struct SculptUndoNode *next, *prev;
char idname[MAX_ID_NAME]; /* name instead of pointer*/
void *node; /* only during push, not valid afterwards! */
float (*co)[3];
short (*no)[3];
int totvert;
/* non-multires */
int maxvert; /* to verify if totvert it still the same */
int *index; /* to restore into right location */
/* multires */
int maxgrid; /* same for grid */
int gridsize; /* same for grid */
int totgrid; /* to restore into right location */
int *grids; /* to restore into right location */
} SculptUndoNode;
static void update_cb(PBVHNode *node, void *data)
{
BLI_pbvh_node_mark_update(node);
}
static void sculpt_undo_restore(bContext *C, ListBase *lb)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
DerivedMesh *dm = mesh_get_derived_final(scene, ob, 0);
SculptSession *ss = ob->sculpt;
SculptUndoNode *unode;
MVert *mvert;
MultiresModifierData *mmd;
int *index;
int i, j, update= 0;
sculpt_update_mesh_elements(scene, ob, 0);
for(unode=lb->first; unode; unode=unode->next) {
if(!(strcmp(unode->idname, ob->id.name)==0))
continue;
if(unode->maxvert) {
/* regular mesh restore */
if(ss->totvert != unode->maxvert)
continue;
index= unode->index;
mvert= ss->mvert;
for(i=0; i<unode->totvert; i++) {
swap_v3_v3(mvert[index[i]].co, unode->co[i]);
mvert[index[i]].flag |= ME_VERT_PBVH_UPDATE;
}
}
else if(unode->maxgrid) {
/* multires restore */
DMGridData **grids, *grid;
float (*co)[3];
int gridsize;
if(dm->getNumGrids(dm) != unode->maxgrid)
continue;
if(dm->getGridSize(dm) != unode->gridsize)
continue;
grids= dm->getGridData(dm);
gridsize= dm->getGridSize(dm);
co = unode->co;
for(j=0; j<unode->totgrid; j++) {
grid= grids[unode->grids[j]];
for(i=0; i<gridsize*gridsize; i++, co++)
swap_v3_v3(grid[i].co, co[0]);
}
}
update= 1;
}
if(update) {
if(ss->kb) sculpt_mesh_to_key(ss->ob, ss->kb);
if(ss->refkb) sculpt_key_to_mesh(ss->refkb, ob);
/* we update all nodes still, should be more clever, but also
needs to work correct when exiting/entering sculpt mode and
the nodes get recreated, though in that case it could do all */
BLI_pbvh_search_callback(ss->tree, NULL, NULL, update_cb, NULL);
BLI_pbvh_update(ss->tree, PBVH_UpdateBB|PBVH_UpdateOriginalBB|PBVH_UpdateRedraw, NULL);
if((mmd=sculpt_multires_active(ob)))
multires_mark_as_modified(ob);
}
}
static void sculpt_undo_free(ListBase *lb)
{
SculptUndoNode *unode;
for(unode=lb->first; unode; unode=unode->next) {
if(unode->co)
MEM_freeN(unode->co);
if(unode->no)
MEM_freeN(unode->no);
if(unode->index)
MEM_freeN(unode->index);
if(unode->grids)
MEM_freeN(unode->grids);
}
}
static SculptUndoNode *sculpt_undo_get_node(SculptSession *ss, PBVHNode *node)
{
ListBase *lb= undo_paint_push_get_list(UNDO_PAINT_MESH);
SculptUndoNode *unode;
if(!lb)
return NULL;
for(unode=lb->first; unode; unode=unode->next)
if(unode->node == node)
return unode;
return NULL;
}
static SculptUndoNode *sculpt_undo_push_node(SculptSession *ss, PBVHNode *node)
{
ListBase *lb= undo_paint_push_get_list(UNDO_PAINT_MESH);
Object *ob= ss->ob;
SculptUndoNode *unode;
int totvert, allvert, totgrid, maxgrid, gridsize, *grids;
/* list is manipulated by multiple threads, so we lock */
BLI_lock_thread(LOCK_CUSTOM1);
if((unode= sculpt_undo_get_node(ss, node))) {
BLI_unlock_thread(LOCK_CUSTOM1);
return unode;
}
unode= MEM_callocN(sizeof(SculptUndoNode), "SculptUndoNode");
strcpy(unode->idname, ob->id.name);
unode->node= node;
BLI_pbvh_node_num_verts(ss->tree, node, &totvert, &allvert);
BLI_pbvh_node_get_grids(ss->tree, node, &grids, &totgrid,
&maxgrid, &gridsize, NULL, NULL);
unode->totvert= totvert;
/* we will use this while sculpting, is mapalloc slow to access then? */
unode->co= MEM_mapallocN(sizeof(float)*3*allvert, "SculptUndoNode.co");
unode->no= MEM_mapallocN(sizeof(short)*3*allvert, "SculptUndoNode.no");
undo_paint_push_count_alloc(UNDO_PAINT_MESH, (sizeof(float)*3 + sizeof(short)*3 + sizeof(int))*allvert);
BLI_addtail(lb, unode);
if(maxgrid) {
/* multires */
unode->maxgrid= maxgrid;
unode->totgrid= totgrid;
unode->gridsize= gridsize;
unode->grids= MEM_mapallocN(sizeof(int)*totgrid, "SculptUndoNode.grids");
}
else {
/* regular mesh */
unode->maxvert= ss->totvert;
unode->index= MEM_mapallocN(sizeof(int)*allvert, "SculptUndoNode.index");
}
BLI_unlock_thread(LOCK_CUSTOM1);
/* copy threaded, hopefully this is the performance critical part */
{
PBVHVertexIter vd;
BLI_pbvh_vertex_iter_begin(ss->tree, node, vd, PBVH_ITER_ALL) {
copy_v3_v3(unode->co[vd.i], vd.co);
if(vd.no) VECCOPY(unode->no[vd.i], vd.no)
else normal_float_to_short_v3(unode->no[vd.i], vd.fno);
if(vd.vert_indices) unode->index[vd.i]= vd.vert_indices[vd.i];
}
BLI_pbvh_vertex_iter_end;
}
if(unode->grids)
memcpy(unode->grids, grids, sizeof(int)*totgrid);
return unode;
}
static void sculpt_undo_push_begin(SculptSession *ss, char *name)
{
undo_paint_push_begin(UNDO_PAINT_MESH, name,
sculpt_undo_restore, sculpt_undo_free);
}
static void sculpt_undo_push_end(SculptSession *ss)
{
ListBase *lb= undo_paint_push_get_list(UNDO_PAINT_MESH);
SculptUndoNode *unode;
/* we don't need normals in the undo stack */
for(unode=lb->first; unode; unode=unode->next) {
if(unode->no) {
MEM_freeN(unode->no);
unode->no= NULL;
}
}
undo_paint_push_end(UNDO_PAINT_MESH);
}
void ED_sculpt_force_update(bContext *C)
{
Object *ob= CTX_data_active_object(C);
if(ob && (ob->mode & OB_MODE_SCULPT))
multires_force_update(ob);
}
/************************ Brush Testing *******************/
typedef struct SculptBrushTest {
float radius_squared;
float location[3];
float dist;
} SculptBrushTest;
static void sculpt_brush_test_init(SculptSession *ss, SculptBrushTest *test)
{
test->radius_squared= ss->cache->radius*ss->cache->radius;
copy_v3_v3(test->location, ss->cache->location);
}
static int sculpt_brush_test(SculptBrushTest *test, float co[3])
{
float distsq, delta[3];
sub_v3_v3v3(delta, co, test->location);
distsq = INPR(delta, delta);
if(distsq < test->radius_squared) {
test->dist = sqrt(distsq);
return 1;
}
return 0;
}
/* ===== Sculpting =====
*
*/
/* Return modified brush strength. Includes the direction of the brush, positive
values pull vertices, negative values push. Uses tablet pressure and a
special multiplier found experimentally to scale the strength factor. */
static float brush_strength(Sculpt *sd, StrokeCache *cache)
{
Brush *brush = paint_brush(&sd->paint);
/* Primary strength input; square it to make lower values more sensitive */
float alpha = brush->alpha * brush->alpha;
float dir= brush->flag & BRUSH_DIR_IN ? -1 : 1;
float pressure= 1;
float flip= cache->flip ? -1:1;
if(brush->flag & BRUSH_ALPHA_PRESSURE)
pressure *= cache->pressure;
switch(brush->sculpt_tool){
case SCULPT_TOOL_DRAW:
case SCULPT_TOOL_INFLATE:
case SCULPT_TOOL_CLAY:
case SCULPT_TOOL_FLATTEN:
case SCULPT_TOOL_LAYER:
return alpha * dir * pressure * flip; /*XXX: not sure why? was multiplied by G.vd->grid */;
case SCULPT_TOOL_SMOOTH:
return alpha * 4 * pressure;
case SCULPT_TOOL_PINCH:
return alpha / 2 * dir * pressure * flip;
case SCULPT_TOOL_GRAB:
return 1;
default:
return 0;
}
}
/* Uses symm to selectively flip any axis of a coordinate. */
static void flip_coord(float out[3], float in[3], const char symm)
{
if(symm & SCULPT_SYMM_X)
out[0]= -in[0];
else
out[0]= in[0];
if(symm & SCULPT_SYMM_Y)
out[1]= -in[1];
else
out[1]= in[1];
if(symm & SCULPT_SYMM_Z)
out[2]= -in[2];
else
out[2]= in[2];
}
/* Get a pixel from the texcache at (px, py) */
static unsigned char get_texcache_pixel(const SculptSession *ss, int px, int py)
{
unsigned *p;
p = ss->texcache + py * ss->texcache_side + px;
return ((unsigned char*)(p))[0];
}
static float get_texcache_pixel_bilinear(const SculptSession *ss, float u, float v)
{
int x, y, x2, y2;
const int tc_max = ss->texcache_side - 1;
float urat, vrat, uopp;
if(u < 0) u = 0;
else if(u >= ss->texcache_side) u = tc_max;
if(v < 0) v = 0;
else if(v >= ss->texcache_side) v = tc_max;
x = floor(u);
y = floor(v);
x2 = x + 1;
y2 = y + 1;
if(x2 > ss->texcache_side) x2 = tc_max;
if(y2 > ss->texcache_side) y2 = tc_max;
urat = u - x;
vrat = v - y;
uopp = 1 - urat;
return ((get_texcache_pixel(ss, x, y) * uopp +
get_texcache_pixel(ss, x2, y) * urat) * (1 - vrat) +
(get_texcache_pixel(ss, x, y2) * uopp +
get_texcache_pixel(ss, x2, y2) * urat) * vrat) / 255.0;
}
/* Return a multiplier for brush strength on a particular vertex. */
static float tex_strength(SculptSession *ss, Brush *br, float *point, const float len)
{
MTex *tex = NULL;
float avg= 1;
if(br->texact >= 0)
tex = br->mtex[br->texact];
if(!tex) {
avg= 1;
}
else if(tex->brush_map_mode == MTEX_MAP_MODE_3D) {
float jnk;
/* Get strength by feeding the vertex
location directly into a texture */
externtex(tex, point, &avg,
&jnk, &jnk, &jnk, &jnk);
}
else if(ss->texcache) {
const float bsize= ss->cache->pixel_radius * 2;
const float rot= tex->rot + ss->cache->rotation;
int px, py;
float flip[3], point_2d[2];
/* If the active area is being applied for symmetry, flip it
across the symmetry axis in order to project it. This insures
that the brush texture will be oriented correctly. */
copy_v3_v3(flip, point);
flip_coord(flip, flip, ss->cache->symmetry);
projectf(ss->cache->mats, flip, point_2d);
/* For Tile and Drag modes, get the 2D screen coordinates of the
and scale them up or down to the texture size. */
if(tex->brush_map_mode == MTEX_MAP_MODE_TILED) {
const int sx= (const int)tex->size[0];
const int sy= (const int)tex->size[1];
float fx= point_2d[0];
float fy= point_2d[1];
float angle= atan2(fy, fx) - rot;
float flen= sqrtf(fx*fx + fy*fy);
if(rot<0.001 && rot>-0.001) {
px= point_2d[0];
py= point_2d[1];
} else {
px= flen * cos(angle) + 2000;
py= flen * sin(angle) + 2000;
}
if(sx != 1)
px %= sx-1;
if(sy != 1)
py %= sy-1;
avg= get_texcache_pixel_bilinear(ss, ss->texcache_side*px/sx, ss->texcache_side*py/sy);
}
else if(tex->brush_map_mode == MTEX_MAP_MODE_FIXED) {
float fx= (point_2d[0] - ss->cache->tex_mouse[0]) / bsize;
float fy= (point_2d[1] - ss->cache->tex_mouse[1]) / bsize;
float angle= atan2(fy, fx) - rot;
float flen= sqrtf(fx*fx + fy*fy);
fx = flen * cos(angle) + 0.5;
fy = flen * sin(angle) + 0.5;
avg= get_texcache_pixel_bilinear(ss, fx * ss->texcache_side, fy * ss->texcache_side);
}
}
avg*= brush_curve_strength(br, len, ss->cache->radius); /* Falloff curve */
return avg;
}
typedef struct {
Sculpt *sd;
SculptSession *ss;
float radius_squared;
ListBase *active_verts;
float area_normal[3];
} SculptSearchSphereData;
/* Test AABB against sphere */
static int sculpt_search_sphere_cb(PBVHNode *node, void *data_v)
{
SculptSearchSphereData *data = data_v;
float *center = data->ss->cache->location, nearest[3];
float t[3], bb_min[3], bb_max[3];
int i;
BLI_pbvh_node_get_BB(node, bb_min, bb_max);
for(i = 0; i < 3; ++i) {
if(bb_min[i] > center[i])
nearest[i] = bb_min[i];
else if(bb_max[i] < center[i])
nearest[i] = bb_max[i];
else
nearest[i] = center[i];
}
sub_v3_v3v3(t, center, nearest);
return t[0] * t[0] + t[1] * t[1] + t[2] * t[2] < data->radius_squared;
}
/* Handles clipping against a mirror modifier and SCULPT_LOCK axis flags */
static void sculpt_clip(Sculpt *sd, SculptSession *ss, float *co, const float val[3])
{
int i;
for(i=0; i<3; ++i) {
if(sd->flags & (SCULPT_LOCK_X << i))
continue;
if((ss->cache->flag & (CLIP_X << i)) && (fabs(co[i]) <= ss->cache->clip_tolerance[i]))
co[i]= 0.0f;
else
co[i]= val[i];
}
}
static void add_norm_if(float view_vec[3], float out[3], float out_flip[3], float fno[3])
{
if((dot_v3v3(view_vec, fno)) > 0) {
add_v3_v3v3(out, out, fno);
} else {
add_v3_v3v3(out_flip, out_flip, fno); /* out_flip is used when out is {0,0,0} */
}
}
/* For draw/layer/flatten; finds average normal for all active vertices */
static void calc_area_normal(Sculpt *sd, SculptSession *ss, float area_normal[3], PBVHNode **nodes, int totnode)
{
PBVH *bvh= ss->tree;
StrokeCache *cache = ss->cache;
const int view = 0; /* XXX: should probably be a flag, not number: brush_type==SCULPT_TOOL_DRAW ? sculptmode_brush()->view : 0; */
float out[3] = {0.0f, 0.0f, 0.0f};
float out_flip[3] = {0.0f, 0.0f, 0.0f};
float out_dir[3];
int n;
copy_v3_v3(out_dir, cache->view_normal_symmetry);
/* threaded loop over nodes */
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
SculptUndoNode *unode;
float fno[3];
float nout[3] = {0.0f, 0.0f, 0.0f};
float nout_flip[3] = {0.0f, 0.0f, 0.0f};
// XXX push instead of get for thread safety in draw
// brush .. lame, but also not harmful really
unode= sculpt_undo_push_node(ss, nodes[n]);
sculpt_brush_test_init(ss, &test);
if(ss->cache->original) {
BLI_pbvh_vertex_iter_begin(bvh, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, unode->co[vd.i])) {
normal_short_to_float_v3(fno, unode->no[vd.i]);
add_norm_if(out_dir, nout, nout_flip, fno);
}
}
BLI_pbvh_vertex_iter_end;
}
else {
BLI_pbvh_vertex_iter_begin(bvh, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
if(vd.no) {
normal_short_to_float_v3(fno, vd.no);
add_norm_if(out_dir, nout, nout_flip, fno);
}
else
add_norm_if(out_dir, nout, nout_flip, vd.fno);
}
}
BLI_pbvh_vertex_iter_end;
}
#pragma omp critical
{
/* we sum per node and add together later for threads */
add_v3_v3v3(out, out, nout);
add_v3_v3v3(out_flip, out_flip, nout_flip);
}
}
if (out[0]==0.0 && out[1]==0.0 && out[2]==0.0) {
copy_v3_v3(out, out_flip);
}
normalize_v3(out);
out[0] = out_dir[0] * view + out[0] * (10-view);
out[1] = out_dir[1] * view + out[1] * (10-view);
out[2] = out_dir[2] * view + out[2] * (10-view);
normalize_v3(out);
copy_v3_v3(area_normal, out);
}
static void do_draw_brush(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode)
{
Brush *brush = paint_brush(&sd->paint);
float offset[3], area_normal[3];
float bstrength= ss->cache->bstrength;
int n;
/* area normal */
calc_area_normal(sd, ss, area_normal, nodes, totnode);
/* offset with as much as possible factored in already */
offset[0]= area_normal[0]*ss->cache->radius*ss->cache->scale[0]*bstrength;
offset[1]= area_normal[1]*ss->cache->radius*ss->cache->scale[1]*bstrength;
offset[2]= area_normal[2]*ss->cache->radius*ss->cache->scale[2]*bstrength;
/* threaded loop over nodes */
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
int update = 0;
sculpt_undo_push_node(ss, nodes[n]);
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
/* offset vertex */
float fade = tex_strength(ss, brush, vd.co, test.dist);
float val[3]= {vd.co[0] + offset[0]*fade,
vd.co[1] + offset[1]*fade,
vd.co[2] + offset[2]*fade};
sculpt_clip(sd, ss, vd.co, val);
if(vd.mvert) vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
update = 1;
}
}
BLI_pbvh_vertex_iter_end;
if (update)
BLI_pbvh_node_mark_update(nodes[n]);
}
}
/* For the smooth brush, uses the neighboring vertices around vert to calculate
a smoothed location for vert. Skips corner vertices (used by only one
polygon.) */
static void neighbor_average(SculptSession *ss, float avg[3], const int vert)
{
int i, skip= -1, total=0;
IndexNode *node= ss->fmap[vert].first;
char ncount= BLI_countlist(&ss->fmap[vert]);
MFace *f;
avg[0] = avg[1] = avg[2] = 0;
/* Don't modify corner vertices */
if(ncount==1) {
copy_v3_v3(avg, ss->mvert[vert].co);
return;
}
while(node){
f= &ss->mface[node->index];
if(f->v4) {
skip= (f->v1==vert?2:
f->v2==vert?3:
f->v3==vert?0:
f->v4==vert?1:-1);
}
for(i=0; i<(f->v4?4:3); ++i) {
if(i != skip && (ncount!=2 || BLI_countlist(&ss->fmap[(&f->v1)[i]]) <= 2)) {
add_v3_v3v3(avg, avg, ss->mvert[(&f->v1)[i]].co);
++total;
}
}
node= node->next;
}
if(total>0)
mul_v3_fl(avg, 1.0f / total);
else
copy_v3_v3(avg, ss->mvert[vert].co);
}
static void do_mesh_smooth_brush(Sculpt *sd, SculptSession *ss, PBVHNode *node)
{
Brush *brush = paint_brush(&sd->paint);
float bstrength= ss->cache->bstrength;
PBVHVertexIter vd;
SculptBrushTest test;
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, node, vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
float fade = tex_strength(ss, brush, vd.co, test.dist)*bstrength;
float avg[3], val[3];
neighbor_average(ss, avg, vd.vert_indices[vd.i]);
val[0] = vd.co[0]+(avg[0]-vd.co[0])*fade;
val[1] = vd.co[1]+(avg[1]-vd.co[1])*fade;
val[2] = vd.co[2]+(avg[2]-vd.co[2])*fade;
sculpt_clip(sd, ss, vd.co, val);
if(vd.mvert) vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BLI_pbvh_vertex_iter_end;
}
static void do_multires_smooth_brush(Sculpt *sd, SculptSession *ss, PBVHNode *node)
{
Brush *brush = paint_brush(&sd->paint);
SculptBrushTest test;
DMGridData **griddata, *data;
DMGridAdjacency *gridadj, *adj;
float bstrength= ss->cache->bstrength;
float co[3], (*tmpgrid)[3];
int v1, v2, v3, v4;
int *grid_indices, totgrid, gridsize, i, x, y;
sculpt_brush_test_init(ss, &test);
BLI_pbvh_node_get_grids(ss->tree, node, &grid_indices, &totgrid,
NULL, &gridsize, &griddata, &gridadj);
#pragma omp critical
tmpgrid= MEM_mallocN(sizeof(float)*3*gridsize*gridsize, "tmpgrid");
for(i = 0; i < totgrid; ++i) {
data = griddata[grid_indices[i]];
adj = &gridadj[grid_indices[i]];
memset(tmpgrid, 0, sizeof(float)*3*gridsize*gridsize);
/* average grid values */
for(y = 0; y < gridsize-1; ++y) {
for(x = 0; x < gridsize-1; ++x) {
v1 = x + y*gridsize;
v2 = (x + 1) + y*gridsize;
v3 = (x + 1) + (y + 1)*gridsize;
v4 = x + (y + 1)*gridsize;
cent_quad_v3(co, data[v1].co, data[v2].co, data[v3].co, data[v4].co);
mul_v3_fl(co, 0.25f);
add_v3_v3(tmpgrid[v1], co);
add_v3_v3(tmpgrid[v2], co);
add_v3_v3(tmpgrid[v3], co);
add_v3_v3(tmpgrid[v4], co);
}
}
/* blend with existing coordinates */
for(y = 0; y < gridsize; ++y) {
for(x = 0; x < gridsize; ++x) {
if(x == 0 && adj->index[0] == -1) continue;
if(x == gridsize - 1 && adj->index[2] == -1) continue;
if(y == 0 && adj->index[3] == -1) continue;
if(y == gridsize - 1 && adj->index[1] == -1) continue;
copy_v3_v3(co, data[x + y*gridsize].co);
if(sculpt_brush_test(&test, co)) {
float fade = tex_strength(ss, brush, co, test.dist)*bstrength;
float avg[3], val[3];
copy_v3_v3(avg, tmpgrid[x + y*gridsize]);
if(x == 0 || x == gridsize - 1)
mul_v3_fl(avg, 2.0f);
if(y == 0 || y == gridsize - 1)
mul_v3_fl(avg, 2.0f);
val[0] = co[0]+(avg[0]-co[0])*fade;
val[1] = co[1]+(avg[1]-co[1])*fade;
val[2] = co[2]+(avg[2]-co[2])*fade;
sculpt_clip(sd, ss, data[x + y*gridsize].co, val);
}
}
}
}
#pragma omp critical
MEM_freeN(tmpgrid);
}
static void do_smooth_brush(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode)
{
int iteration, n;
for(iteration = 0; iteration < 2; ++iteration) {
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
sculpt_undo_push_node(ss, nodes[n]);
if(ss->fmap)
do_mesh_smooth_brush(sd, ss, nodes[n]);
else
do_multires_smooth_brush(sd, ss, nodes[n]);
BLI_pbvh_node_mark_update(nodes[n]);
}
if(!ss->fmap)
multires_stitch_grids(ss->ob);
}
}
static void do_pinch_brush(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode)
{
Brush *brush = paint_brush(&sd->paint);
float bstrength= ss->cache->bstrength;
int n;
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
sculpt_undo_push_node(ss, nodes[n]);
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
float fade = tex_strength(ss, brush, vd.co, test.dist)*bstrength;
float val[3]= {vd.co[0]+(test.location[0]-vd.co[0])*fade,
vd.co[1]+(test.location[1]-vd.co[1])*fade,
vd.co[2]+(test.location[2]-vd.co[2])*fade};
sculpt_clip(sd, ss, vd.co, val);
if(vd.mvert) vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BLI_pbvh_vertex_iter_end;
BLI_pbvh_node_mark_update(nodes[n]);
}
}
static void do_grab_brush(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode)
{
Brush *brush = paint_brush(&sd->paint);
float bstrength= ss->cache->bstrength;
float grab_delta[3];
int n;
copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry);
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
float (*origco)[3];
origco= sculpt_undo_push_node(ss, nodes[n])->co;
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, origco[vd.i])) {
float fade = tex_strength(ss, brush, origco[vd.i], test.dist)*bstrength;
float add[3]= {vd.co[0]+fade*grab_delta[0],
vd.co[1]+fade*grab_delta[1],
vd.co[2]+fade*grab_delta[2]};
sculpt_clip(sd, ss, vd.co, add);
if(vd.mvert) vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BLI_pbvh_vertex_iter_end;
BLI_pbvh_node_mark_update(nodes[n]);
}
}
static void do_layer_brush(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode)
{
Brush *brush = paint_brush(&sd->paint);
float bstrength= ss->cache->bstrength;
float area_normal[3], offset[3];
float lim= ss->cache->radius / 4;
int n;
/* XXX not working yet for multires */
if(ss->multires)
return;
if(ss->cache->flip)
lim = -lim;
calc_area_normal(sd, ss, area_normal, nodes, totnode);
offset[0]= ss->cache->scale[0]*area_normal[0];
offset[1]= ss->cache->scale[1]*area_normal[1];
offset[2]= ss->cache->scale[2]*area_normal[2];
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
float (*origco)[3];
origco= sculpt_undo_push_node(ss, nodes[n])->co;
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
float fade = tex_strength(ss, brush, vd.co, test.dist)*bstrength;
int index= vd.vert_indices[vd.i];
float *disp= &ss->layer_disps[index];
float val[3];
*disp+= fade;
/* Don't let the displacement go past the limit */
if((lim < 0 && *disp < lim) || (lim > 0 && *disp > lim))
*disp = lim;
if(ss->layer_co && (brush->flag & BRUSH_PERSISTENT)) {
/* persistent base */
val[0] = ss->layer_co[index][0] + (*disp)*offset[0];
val[1] = ss->layer_co[index][1] + (*disp)*offset[1];
val[2] = ss->layer_co[index][2] + (*disp)*offset[2];
}
else {
val[0] = origco[vd.i][0] + (*disp)*offset[0];
val[1] = origco[vd.i][1] + (*disp)*offset[1];
val[2] = origco[vd.i][2] + (*disp)*offset[2];
}
sculpt_clip(sd, ss, vd.co, val);
if(vd.mvert) vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BLI_pbvh_vertex_iter_end;
BLI_pbvh_node_mark_update(nodes[n]);
}
}
static void do_inflate_brush(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode)
{
Brush *brush = paint_brush(&sd->paint);
float bstrength= ss->cache->bstrength;
int n;
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
sculpt_undo_push_node(ss, nodes[n]);
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
float fade = tex_strength(ss, brush, vd.co, test.dist)*bstrength;
float add[3];
if(vd.fno) copy_v3_v3(add, vd.fno);
else normal_short_to_float_v3(add, vd.no);
mul_v3_fl(add, fade * ss->cache->radius);
add[0]*= ss->cache->scale[0];
add[1]*= ss->cache->scale[1];
add[2]*= ss->cache->scale[2];
add_v3_v3v3(add, add, vd.co);
sculpt_clip(sd, ss, vd.co, add);
if(vd.mvert) vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BLI_pbvh_vertex_iter_end;
BLI_pbvh_node_mark_update(nodes[n]);
}
}
static void calc_flatten_center(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode, float co[3])
{
float outer_dist[FLATTEN_SAMPLE_SIZE];
float outer_co[FLATTEN_SAMPLE_SIZE][3];
int i, n;
for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i) {
zero_v3(outer_co[i]);
outer_dist[i]= -1.0f;
}
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
int j;
sculpt_undo_push_node(ss, nodes[n]);
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
for(j = 0; j < FLATTEN_SAMPLE_SIZE; ++j) {
if(test.dist > outer_dist[j]) {
copy_v3_v3(outer_co[j], vd.co);
outer_dist[j] = test.dist;
break;
}
}
}
}
BLI_pbvh_vertex_iter_end;
BLI_pbvh_node_mark_update(nodes[n]);
}
co[0] = co[1] = co[2] = 0.0f;
for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i)
if(outer_dist[i] >= 0.0f)
add_v3_v3v3(co, co, outer_co[i]);
mul_v3_fl(co, 1.0f / FLATTEN_SAMPLE_SIZE);
}
/* Projects a point onto a plane along the plane's normal */
static void point_plane_project(float intr[3], float co[3], float plane_normal[3], float plane_center[3])
{
float p1[3], sub1[3], sub2[3];
/* Find the intersection between squash-plane and vertex (along the area normal) */
sub_v3_v3v3(p1, co, plane_normal);
sub_v3_v3v3(sub1, plane_center, p1);
sub_v3_v3v3(sub2, co, p1);
sub_v3_v3v3(intr, co, p1);
mul_v3_fl(intr, dot_v3v3(plane_normal, sub1) / dot_v3v3(plane_normal, sub2));
add_v3_v3v3(intr, intr, p1);
}
static int plane_point_side(float co[3], float plane_normal[3], float plane_center[3], int flip)
{
float delta[3];
float d;
sub_v3_v3v3(delta, co, plane_center);
d = dot_v3v3(plane_normal, delta);
if(flip)
d = -d;
return d <= 0.0f;
}
static void do_flatten_clay_brush(Sculpt *sd, SculptSession *ss, PBVHNode **nodes, int totnode, int clay)
{
/* area_normal and cntr define the plane towards which vertices are squashed */
Brush *brush = paint_brush(&sd->paint);
float bstrength= ss->cache->bstrength;
float area_normal[3];
float cntr[3], cntr2[3], bstr = 0;
int n, flip = 0;
calc_area_normal(sd, ss, area_normal, nodes, totnode);
calc_flatten_center(sd, ss, nodes, totnode, cntr);
if(clay) {
bstr= brush_strength(sd, ss->cache);
/* Limit clay application to here */
cntr2[0]=cntr[0]+area_normal[0]*bstr*ss->cache->scale[0];
cntr2[1]=cntr[1]+area_normal[1]*bstr*ss->cache->scale[1];
cntr2[2]=cntr[2]+area_normal[2]*bstr*ss->cache->scale[2];
flip = bstr < 0;
}
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
PBVHVertexIter vd;
SculptBrushTest test;
sculpt_undo_push_node(ss, nodes[n]);
sculpt_brush_test_init(ss, &test);
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
if(sculpt_brush_test(&test, vd.co)) {
float intr[3], val[3];
if(!clay || plane_point_side(vd.co, area_normal, cntr2, flip)) {
const float fade = tex_strength(ss, brush, vd.co, test.dist)*bstrength;
/* Find the intersection between squash-plane and vertex (along the area normal) */
point_plane_project(intr, vd.co, area_normal, cntr);
sub_v3_v3v3(val, intr, vd.co);
if(clay) {
if(bstr > FLT_EPSILON)
mul_v3_fl(val, fade / bstr);
else
mul_v3_fl(val, fade);
/* Clay displacement */
val[0]+=area_normal[0] * ss->cache->scale[0]*fade;
val[1]+=area_normal[1] * ss->cache->scale[1]*fade;
val[2]+=area_normal[2] * ss->cache->scale[2]*fade;
}
else
mul_v3_fl(val, fabs(fade));
add_v3_v3v3(val, val, vd.co);
sculpt_clip(sd, ss, vd.co, val);
if(vd.mvert) vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
}
BLI_pbvh_vertex_iter_end;
BLI_pbvh_node_mark_update(nodes[n]);
}
}
static void do_brush_action(Sculpt *sd, SculptSession *ss, StrokeCache *cache)
{
SculptSearchSphereData data;
Brush *brush = paint_brush(&sd->paint);
PBVHNode **nodes= NULL;
rctf mr;
ARegion *ar = cache->vc->ar;
float xrad, yrad;
int totnode;
xrad = cache->pixel_radius+5.0f;
yrad = cache->pixel_radius+5.0f;
mr.xmin = cache->mouse[0] - xrad - cache->vc->ar->winrct.xmin;
mr.xmax = cache->mouse[0] + xrad - cache->vc->ar->winrct.xmin;
mr.ymin = cache->mouse[1] - yrad - cache->vc->ar->winrct.ymin;
mr.ymax = cache->mouse[1] + yrad - cache->vc->ar->winrct.ymin;
cache->prect.xmin = MIN2(cache->prect.xmin, mr.xmin);
cache->prect.xmax = MAX2(cache->prect.xmax, mr.xmax);
cache->prect.ymin = MIN2(cache->prect.ymin, mr.ymin);
cache->prect.ymax = MAX2(cache->prect.ymax, mr.ymax);
data.ss = ss;
data.sd = sd;
data.radius_squared = ss->cache->radius * ss->cache->radius;
/* Build a list of all nodes that are potentially within the brush's
area of influence */
if(brush->sculpt_tool == SCULPT_TOOL_GRAB) {
if(cache->first_time) {
/* For the grab tool we store these nodes once in the beginning
and then reuse them. */
BLI_pbvh_search_gather(ss->tree, sculpt_search_sphere_cb, &data,
&nodes, &totnode);
ss->cache->grab_active_nodes[ss->cache->symmetry]= nodes;
ss->cache->grab_active_totnode[ss->cache->symmetry]= totnode;
copy_v3_v3(ss->cache->grab_active_location[ss->cache->symmetry], ss->cache->location);
}
else {
nodes= ss->cache->grab_active_nodes[ss->cache->symmetry];
totnode= ss->cache->grab_active_totnode[ss->cache->symmetry];
copy_v3_v3(ss->cache->location, ss->cache->grab_active_location[ss->cache->symmetry]);
}
}
else {
BLI_pbvh_search_gather(ss->tree, sculpt_search_sphere_cb, &data,
&nodes, &totnode);
}
/* Only act if some verts are inside the brush area */
if(totnode) {
/* Apply one type of brush action */
switch(brush->sculpt_tool){
case SCULPT_TOOL_DRAW:
do_draw_brush(sd, ss, nodes, totnode);
break;
case SCULPT_TOOL_SMOOTH:
do_smooth_brush(sd, ss, nodes, totnode);
break;
case SCULPT_TOOL_PINCH:
do_pinch_brush(sd, ss, nodes, totnode);
break;
case SCULPT_TOOL_INFLATE:
do_inflate_brush(sd, ss, nodes, totnode);
break;
case SCULPT_TOOL_GRAB:
do_grab_brush(sd, ss, nodes, totnode);
break;
case SCULPT_TOOL_LAYER:
do_layer_brush(sd, ss, nodes, totnode);
break;
case SCULPT_TOOL_FLATTEN:
do_flatten_clay_brush(sd, ss, nodes, totnode, 0);
break;
case SCULPT_TOOL_CLAY:
do_flatten_clay_brush(sd, ss, nodes, totnode, 1);
break;
}
/* copy the modified vertices from mesh to the active key */
if(ss->kb) mesh_to_key(ss->ob->data, ss->kb);
if((brush->sculpt_tool != SCULPT_TOOL_GRAB) && nodes)
MEM_freeN(nodes);
}
}
/* Flip all the editdata across the axis/axes specified by symm. Used to
calculate multiple modifications to the mesh when symmetry is enabled. */
static void calc_brushdata_symm(StrokeCache *cache, const char symm)
{
flip_coord(cache->location, cache->true_location, symm);
flip_coord(cache->view_normal_symmetry, cache->view_normal, symm);
flip_coord(cache->grab_delta_symmetry, cache->grab_delta, symm);
cache->symmetry= symm;
}
static void do_symmetrical_brush_actions(Sculpt *sd, SculptSession *ss)
{
StrokeCache *cache = ss->cache;
const char symm = sd->flags & 7;
int i;
copy_v3_v3(cache->location, cache->true_location);
copy_v3_v3(cache->grab_delta_symmetry, cache->grab_delta);
cache->symmetry = 0;
cache->bstrength = brush_strength(sd, cache);
do_brush_action(sd, ss, cache);
for(i = 1; i <= symm; ++i) {
if(symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5))) {
calc_brushdata_symm(cache, i);
do_brush_action(sd, ss, cache);
}
}
cache->first_time = 0;
}
static void sculpt_update_tex(Sculpt *sd, SculptSession *ss)
{
Brush *brush = paint_brush(&sd->paint);
if(ss->texcache) {
MEM_freeN(ss->texcache);
ss->texcache= NULL;
}
/* Need to allocate a bigger buffer for bigger brush size */
ss->texcache_side = brush->size * 2;
if(!ss->texcache || ss->texcache_side > ss->texcache_actual) {
ss->texcache = brush_gen_texture_cache(brush, brush->size);
ss->texcache_actual = ss->texcache_side;
}
}
/* Checks whether full update mode (slower) needs to be used to work with modifiers */
char sculpt_modifiers_active(Object *ob)
{
ModifierData *md;
for(md= modifiers_getVirtualModifierList(ob); md; md= md->next) {
if(modifier_isEnabled(md, eModifierMode_Realtime))
if(!ELEM(md->type, eModifierType_Multires, eModifierType_ShapeKey))
return 1;
}
return 0;
}
/* Sculpt mode handles multires differently from regular meshes, but only if
it's the last modifier on the stack and it is not on the first level */
struct MultiresModifierData *sculpt_multires_active(Object *ob)
{
ModifierData *md, *nmd;
for(md= modifiers_getVirtualModifierList(ob); md; md= md->next) {
if(md->type == eModifierType_Multires) {
MultiresModifierData *mmd= (MultiresModifierData*)md;
/* Check if any of the modifiers after multires are active
* if not it can use the multires struct */
for (nmd= md->next; nmd; nmd= nmd->next)
if(nmd->mode & eModifierMode_Realtime)
break;
if(!nmd && mmd->sculptlvl > 0)
return mmd;
}
}
return NULL;
}
void sculpt_key_to_mesh(KeyBlock *kb, Object *ob)
{
Mesh *me= ob->data;
key_to_mesh(kb, me);
mesh_calc_normals(me->mvert, me->totvert, me->mface, me->totface, NULL);
}
void sculpt_mesh_to_key(Object *ob, KeyBlock *kb)
{
Mesh *me= ob->data;
mesh_to_key(me, kb);
}
void sculpt_update_mesh_elements(Scene *scene, Object *ob, int need_fmap)
{
DerivedMesh *dm = mesh_get_derived_final(scene, ob, 0);
SculptSession *ss = ob->sculpt;
ss->ob= ob;
if((ob->shapeflag & OB_SHAPE_LOCK) && !sculpt_multires_active(ob)) {
ss->kb= ob_get_keyblock(ob);
ss->refkb= ob_get_reference_keyblock(ob);
}
else {
ss->kb= NULL;
ss->refkb= NULL;
}
/* need to make PBVH with shape key coordinates */
if(ss->kb) sculpt_key_to_mesh(ss->kb, ss->ob);
if((ss->multires = sculpt_multires_active(ob))) {
ss->totvert = dm->getNumVerts(dm);
ss->totface = dm->getNumTessFaces(dm);
ss->mvert= NULL;
ss->mface= NULL;
ss->face_normals= NULL;
}
else {
Mesh *me = get_mesh(ob);
ss->totvert = me->totvert;
ss->totface = me->totface;
ss->mvert = me->mvert;
ss->mface = me->mface;
ss->face_normals = NULL;
}
ss->tree = dm->getPBVH(ob, dm);
ss->fmap = (need_fmap && dm->getFaceMap)? dm->getFaceMap(dm): NULL;
}
static int sculpt_mode_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
return ob && ob->mode & OB_MODE_SCULPT;
}
int sculpt_poll(bContext *C)
{
return sculpt_mode_poll(C) && paint_poll(C);
}
static char *sculpt_tool_name(Sculpt *sd)
{
Brush *brush = paint_brush(&sd->paint);
switch(brush->sculpt_tool) {
case SCULPT_TOOL_DRAW:
return "Draw Brush"; break;
case SCULPT_TOOL_SMOOTH:
return "Smooth Brush"; break;
case SCULPT_TOOL_PINCH:
return "Pinch Brush"; break;
case SCULPT_TOOL_INFLATE:
return "Inflate Brush"; break;
case SCULPT_TOOL_GRAB:
return "Grab Brush"; break;
case SCULPT_TOOL_LAYER:
return "Layer Brush"; break;
case SCULPT_TOOL_FLATTEN:
return "Flatten Brush"; break;
default:
return "Sculpting"; break;
}
}
/**** Radial control ****/
static int sculpt_radial_control_invoke(bContext *C, wmOperator *op, wmEvent *event)
{
Paint *p = paint_get_active(CTX_data_scene(C));
Brush *brush = paint_brush(p);
WM_paint_cursor_end(CTX_wm_manager(C), p->paint_cursor);
p->paint_cursor = NULL;
brush_radial_control_invoke(op, brush, 1);
return WM_radial_control_invoke(C, op, event);
}
static int sculpt_radial_control_modal(bContext *C, wmOperator *op, wmEvent *event)
{
int ret = WM_radial_control_modal(C, op, event);
if(ret != OPERATOR_RUNNING_MODAL)
paint_cursor_start(C, sculpt_poll);
return ret;
}
static int sculpt_radial_control_exec(bContext *C, wmOperator *op)
{
Brush *brush = paint_brush(&CTX_data_tool_settings(C)->sculpt->paint);
int ret = brush_radial_control_exec(op, brush, 1);
WM_event_add_notifier(C, NC_BRUSH|NA_EDITED, brush);
return ret;
}
static void SCULPT_OT_radial_control(wmOperatorType *ot)
{
WM_OT_radial_control_partial(ot);
ot->name= "Sculpt Radial Control";
ot->idname= "SCULPT_OT_radial_control";
ot->invoke= sculpt_radial_control_invoke;
ot->modal= sculpt_radial_control_modal;
ot->exec= sculpt_radial_control_exec;
ot->poll= sculpt_poll;
ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING;
}
/**** Operator for applying a stroke (various attributes including mouse path)
using the current brush. ****/
static float unproject_brush_radius(Object *ob, ViewContext *vc, float center[3], float offset)
{
float delta[3], scale, loc[3];
mul_v3_m4v3(loc, ob->obmat, center);
initgrabz(vc->rv3d, loc[0], loc[1], loc[2]);
window_to_3d_delta(vc->ar, delta, offset, 0);
scale= fabsf(mat4_to_scale(ob->obmat));
scale= (scale == 0.0f)? 1.0f: scale;
return len_v3(delta)/scale;
}
static void sculpt_cache_free(StrokeCache *cache)
{
int i;
if(cache->face_norms)
MEM_freeN(cache->face_norms);
if(cache->mats)
MEM_freeN(cache->mats);
for(i = 0; i < 8; ++i) {
if(cache->grab_active_nodes[i])
MEM_freeN(cache->grab_active_nodes[i]);
}
MEM_freeN(cache);
}
/* Initialize the stroke cache invariants from operator properties */
static void sculpt_update_cache_invariants(Sculpt *sd, SculptSession *ss, bContext *C, wmOperator *op)
{
StrokeCache *cache = MEM_callocN(sizeof(StrokeCache), "stroke cache");
Brush *brush = paint_brush(&sd->paint);
ViewContext *vc = paint_stroke_view_context(op->customdata);
int i;
ss->cache = cache;
cache->prect.xmin = FLT_MAX;
cache->prect.xmax = -FLT_MAX;
cache->prect.ymin = FLT_MAX;
cache->prect.ymax = -FLT_MAX;
RNA_float_get_array(op->ptr, "scale", cache->scale);
cache->flag = RNA_int_get(op->ptr, "flag");
RNA_float_get_array(op->ptr, "clip_tolerance", cache->clip_tolerance);
RNA_float_get_array(op->ptr, "initial_mouse", cache->initial_mouse);
copy_v2_v2(cache->mouse, cache->initial_mouse);
copy_v2_v2(cache->tex_mouse, cache->initial_mouse);
/* Truly temporary data that isn't stored in properties */
cache->vc = vc;
cache->brush = brush;
cache->mats = MEM_callocN(sizeof(bglMats), "sculpt bglMats");
view3d_get_transformation(vc->ar, vc->rv3d, vc->obact, cache->mats);
/* Initialize layer brush displacements and persistent coords */
if(brush->sculpt_tool == SCULPT_TOOL_LAYER && !ss->multires) {
if(!ss->layer_disps || !(brush->flag & BRUSH_PERSISTENT)) {
if(ss->layer_disps)
MEM_freeN(ss->layer_disps);
ss->layer_disps = MEM_callocN(sizeof(float) * ss->totvert, "layer brush displacements");
}
if(!ss->layer_co && (brush->flag & BRUSH_PERSISTENT)) {
if(!ss->layer_co)
ss->layer_co= MEM_mallocN(sizeof(float) * 3 * ss->totvert,
"sculpt mesh vertices copy");
for(i = 0; i < ss->totvert; ++i)
copy_v3_v3(ss->layer_co[i], ss->mvert[i].co);
}
}
/* Make copies of the mesh vertex locations and normals for some tools */
if(brush->flag & BRUSH_ANCHORED) {
if(ss->face_normals) {
float *fn = ss->face_normals;
cache->face_norms= MEM_mallocN(sizeof(float) * 3 * ss->totface, "Sculpt face norms");
for(i = 0; i < ss->totface; ++i, fn += 3)
copy_v3_v3(cache->face_norms[i], fn);
}
cache->original = 1;
}
if(ELEM3(brush->sculpt_tool, SCULPT_TOOL_DRAW, SCULPT_TOOL_LAYER, SCULPT_TOOL_INFLATE))
if(!(brush->flag & BRUSH_ACCUMULATE))
cache->original = 1;
cache->rotation = 0;
cache->first_time = 1;
}
/* Initialize the stroke cache variants from operator properties */
static void sculpt_update_cache_variants(Sculpt *sd, SculptSession *ss, struct PaintStroke *stroke, PointerRNA *ptr)
{
StrokeCache *cache = ss->cache;
Brush *brush = paint_brush(&sd->paint);
int dx, dy;
if(!(brush->flag & BRUSH_ANCHORED) || cache->first_time)
RNA_float_get_array(ptr, "location", cache->true_location);
cache->flip = RNA_boolean_get(ptr, "flip");
RNA_float_get_array(ptr, "mouse", cache->mouse);
cache->pressure = RNA_float_get(ptr, "pressure");
/* Truly temporary data that isn't stored in properties */
cache->previous_pixel_radius = cache->pixel_radius;
cache->pixel_radius = brush->size;
if(cache->first_time)
cache->initial_radius = unproject_brush_radius(ss->ob, cache->vc, cache->true_location, brush->size);
if(brush->flag & BRUSH_SIZE_PRESSURE) {
cache->pixel_radius *= cache->pressure;
cache->radius = cache->initial_radius * cache->pressure;
}
else
cache->radius = cache->initial_radius;
if(!(brush->flag & BRUSH_ANCHORED))
copy_v2_v2(cache->tex_mouse, cache->mouse);
if(brush->flag & BRUSH_ANCHORED) {
dx = cache->mouse[0] - cache->initial_mouse[0];
dy = cache->mouse[1] - cache->initial_mouse[1];
cache->pixel_radius = sqrt(dx*dx + dy*dy);
cache->radius = unproject_brush_radius(ss->ob, paint_stroke_view_context(stroke),
cache->true_location, cache->pixel_radius);
cache->rotation = atan2(dy, dx);
}
else if(brush->flag & BRUSH_RAKE) {
int update;
dx = cache->last_rake[0] - cache->mouse[0];
dy = cache->last_rake[1] - cache->mouse[1];
update = dx*dx + dy*dy > 100;
/* To prevent jitter, only update the angle if the mouse has moved over 10 pixels */
if(update && !cache->first_time)
cache->rotation = M_PI_2 + atan2(dy, dx);
if(update || cache->first_time) {
cache->last_rake[0] = cache->mouse[0];
cache->last_rake[1] = cache->mouse[1];
}
}
/* Find the grab delta */
if(brush->sculpt_tool == SCULPT_TOOL_GRAB) {
float grab_location[3];
if(cache->first_time)
copy_v3_v3(cache->orig_grab_location, cache->true_location);
initgrabz(cache->vc->rv3d, cache->orig_grab_location[0], cache->orig_grab_location[1], cache->orig_grab_location[2]);
window_to_3d_delta(cache->vc->ar, grab_location, cache->mouse[0], cache->mouse[1]);
if(!cache->first_time)
sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location);
copy_v3_v3(cache->old_grab_location, grab_location);
}
}
static void sculpt_stroke_modifiers_check(bContext *C, SculptSession *ss)
{
if(sculpt_modifiers_active(ss->ob))
sculpt_update_mesh_elements(CTX_data_scene(C), ss->ob, 0); // XXX brush->sculpt_tool == SCULPT_TOOL_SMOOTH);
}
typedef struct {
SculptSession *ss;
float *ray_start, *ray_normal;
int hit;
float dist;
int original;
} SculptRaycastData;
void sculpt_raycast_cb(PBVHNode *node, void *data_v)
{
SculptRaycastData *srd = data_v;
float (*origco)[3]= NULL;
if(srd->original && srd->ss->cache) {
/* intersect with coordinates from before we started stroke */
SculptUndoNode *unode= sculpt_undo_get_node(srd->ss, node);
origco= (unode)? unode->co: NULL;
}
srd->hit |= BLI_pbvh_node_raycast(srd->ss->tree, node, origco,
srd->ray_start, srd->ray_normal, &srd->dist);
}
/* Do a raycast in the tree to find the 3d brush location
(This allows us to ignore the GL depth buffer)
Returns 0 if the ray doesn't hit the mesh, non-zero otherwise
*/
int sculpt_stroke_get_location(bContext *C, struct PaintStroke *stroke, float out[3], float mouse[2])
{
ViewContext *vc = paint_stroke_view_context(stroke);
SculptSession *ss= vc->obact->sculpt;
StrokeCache *cache= ss->cache;
float ray_start[3], ray_normal[3];
float obimat[4][4];
float mval[2] = {mouse[0] - vc->ar->winrct.xmin,
mouse[1] - vc->ar->winrct.ymin};
SculptRaycastData srd;
sculpt_stroke_modifiers_check(C, ss);
viewray(vc->ar, vc->v3d, mval, ray_start, ray_normal);
invert_m4_m4(obimat, ss->ob->obmat);
mul_m4_v3(obimat, ray_start);
mul_mat3_m4_v3(obimat, ray_normal);
normalize_v3(ray_normal);
srd.ss = vc->obact->sculpt;
srd.ray_start = ray_start;
srd.ray_normal = ray_normal;
srd.dist = FLT_MAX;
srd.hit = 0;
srd.original = (cache)? cache->original: 0;
BLI_pbvh_raycast(ss->tree, sculpt_raycast_cb, &srd,
ray_start, ray_normal, srd.original);
copy_v3_v3(out, ray_normal);
mul_v3_fl(out, srd.dist);
add_v3_v3v3(out, out, ray_start);
return srd.hit;
}
/* Initialize stroke operator properties */
static void sculpt_brush_stroke_init_properties(bContext *C, wmOperator *op, wmEvent *event, SculptSession *ss)
{
Object *ob= CTX_data_active_object(C);
ModifierData *md;
float scale[3], clip_tolerance[3] = {0,0,0};
float mouse[2];
int flag = 0;
/* Set scaling adjustment */
scale[0] = 1.0f / ob->size[0];
scale[1] = 1.0f / ob->size[1];
scale[2] = 1.0f / ob->size[2];
RNA_float_set_array(op->ptr, "scale", scale);
/* Initialize mirror modifier clipping */
for(md= ob->modifiers.first; md; md= md->next) {
if(md->type==eModifierType_Mirror && (md->mode & eModifierMode_Realtime)) {
const MirrorModifierData *mmd = (MirrorModifierData*) md;
/* Mark each axis that needs clipping along with its tolerance */
if(mmd->flag & MOD_MIR_CLIPPING) {
flag |= CLIP_X << mmd->axis;
if(mmd->tolerance > clip_tolerance[mmd->axis])
clip_tolerance[mmd->axis] = mmd->tolerance;
}
}
}
RNA_int_set(op->ptr, "flag", flag);
RNA_float_set_array(op->ptr, "clip_tolerance", clip_tolerance);
/* Initial mouse location */
mouse[0] = event->x;
mouse[1] = event->y;
RNA_float_set_array(op->ptr, "initial_mouse", mouse);
}
static int sculpt_brush_stroke_init(bContext *C, ReportList *reports)
{
Scene *scene= CTX_data_scene(C);
Object *ob= CTX_data_active_object(C);
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
SculptSession *ss = CTX_data_active_object(C)->sculpt;
Brush *brush = paint_brush(&sd->paint);
if(ob_get_key(ob) && !(ob->shapeflag & OB_SHAPE_LOCK)) {
BKE_report(reports, RPT_ERROR, "Shape key sculpting requires a locked shape.");
return 0;
}
view3d_operator_needs_opengl(C);
/* TODO: Shouldn't really have to do this at the start of every
stroke, but sculpt would need some sort of notification when
changes are made to the texture. */
sculpt_update_tex(sd, ss);
sculpt_update_mesh_elements(scene, ob, brush->sculpt_tool == SCULPT_TOOL_SMOOTH);
return 1;
}
static void sculpt_restore_mesh(Sculpt *sd, SculptSession *ss)
{
StrokeCache *cache = ss->cache;
Brush *brush = paint_brush(&sd->paint);
int i;
/* Restore the mesh before continuing with anchored stroke */
if(brush->flag & BRUSH_ANCHORED) {
PBVHNode **nodes;
int n, totnode;
BLI_pbvh_search_gather(ss->tree, NULL, NULL, &nodes, &totnode);
#pragma omp parallel for private(n) schedule(static)
for(n=0; n<totnode; n++) {
SculptUndoNode *unode;
unode= sculpt_undo_get_node(ss, nodes[n]);
if(unode) {
PBVHVertexIter vd;
BLI_pbvh_vertex_iter_begin(ss->tree, nodes[n], vd, PBVH_ITER_UNIQUE) {
copy_v3_v3(vd.co, unode->co[vd.i]);
if(vd.no) VECCOPY(vd.no, unode->no[vd.i])
else normal_short_to_float_v3(vd.fno, unode->no[vd.i]);
}
BLI_pbvh_vertex_iter_end;
}
}
if(ss->face_normals) {
float *fn = ss->face_normals;
for(i = 0; i < ss->totface; ++i, fn += 3)
copy_v3_v3(fn, cache->face_norms[i]);
}
if(brush->sculpt_tool == SCULPT_TOOL_LAYER && !ss->multires)
memset(ss->layer_disps, 0, sizeof(float) * ss->totvert);
}
}
static void sculpt_flush_update(bContext *C)
{
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
ARegion *ar = CTX_wm_region(C);
MultiresModifierData *mmd = ss->multires;
int redraw = 0;
if(mmd)
multires_mark_as_modified(ob);
if(sculpt_modifiers_active(ob)) {
DAG_id_flush_update(&ob->id, OB_RECALC_DATA);
ED_region_tag_redraw(ar);
}
else {
rcti r;
int off = 0;
#if 0 /*debug code to expand the partial redraw rect by 400 every other frame*/
static int c = 0;
c++;
if (c % 2 == 0)
off = 400;
else off = 0;
#endif
BLI_pbvh_update(ss->tree, PBVH_UpdateBB, NULL);
redraw = sculpt_get_redraw_rect(ar, CTX_wm_region_view3d(C), ob, &r);
if(redraw) {
r.xmin += ar->winrct.xmin + 1 + off;
r.xmax += ar->winrct.xmin - 1 - off;
r.ymin += ar->winrct.ymin + 1 + off;
r.ymax += ar->winrct.ymin - 1 - off;
ss->partial_redraw = 1;
ED_region_tag_redraw_partial(ar, &r);
}
}
}
static int sculpt_stroke_test_start(bContext *C, struct wmOperator *op, wmEvent *event)
{
float mouse[2] = {event->x, event->y}, location[3];
int over_mesh;
over_mesh = sculpt_stroke_get_location(C, op->customdata, location, mouse);
/* Don't start the stroke until mouse goes over the mesh */
if(over_mesh) {
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C));
sculpt_brush_stroke_init_properties(C, op, event, ss);
sculpt_update_cache_invariants(sd, ss, C, op);
sculpt_undo_push_begin(ss, sculpt_tool_name(sd));
return 1;
}
else
return 0;
}
static void sculpt_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
SculptSession *ss = CTX_data_active_object(C)->sculpt;
sculpt_stroke_modifiers_check(C, ss);
sculpt_update_cache_variants(sd, ss, stroke, itemptr);
sculpt_restore_mesh(sd, ss);
do_symmetrical_brush_actions(sd, ss);
/* Cleanup */
sculpt_flush_update(C);
}
static void sculpt_stroke_done(bContext *C, struct PaintStroke *stroke)
{
Object *ob= CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
/* Finished */
if(ss->cache) {
sculpt_stroke_modifiers_check(C, ss);
sculpt_cache_free(ss->cache);
ss->cache = NULL;
sculpt_undo_push_end(ss);
BLI_pbvh_update(ss->tree, PBVH_UpdateOriginalBB, NULL);
if(ss->refkb) sculpt_key_to_mesh(ss->refkb, ob);
ss->partial_redraw = 0;
WM_event_add_notifier(C, NC_OBJECT|ND_DRAW, ob);
}
}
static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, wmEvent *event)
{
if(!sculpt_brush_stroke_init(C, op->reports))
return OPERATOR_CANCELLED;
op->customdata = paint_stroke_new(C, sculpt_stroke_get_location,
sculpt_stroke_test_start,
sculpt_stroke_update_step,
sculpt_stroke_done);
/* add modal handler */
WM_event_add_modal_handler(C, op);
op->type->modal(C, op, event);
return OPERATOR_RUNNING_MODAL;
}
static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
SculptSession *ss = CTX_data_active_object(C)->sculpt;
if(!sculpt_brush_stroke_init(C, op->reports))
return OPERATOR_CANCELLED;
op->customdata = paint_stroke_new(C, sculpt_stroke_get_location, sculpt_stroke_test_start,
sculpt_stroke_update_step, sculpt_stroke_done);
sculpt_update_cache_invariants(sd, ss, C, op);
paint_stroke_exec(C, op);
sculpt_flush_update(C);
sculpt_cache_free(ss->cache);
return OPERATOR_FINISHED;
}
static void SCULPT_OT_brush_stroke(wmOperatorType *ot)
{
ot->flag |= OPTYPE_REGISTER;
/* identifiers */
ot->name= "Sculpt Mode";
ot->idname= "SCULPT_OT_brush_stroke";
/* api callbacks */
ot->invoke= sculpt_brush_stroke_invoke;
ot->modal= paint_stroke_modal;
ot->exec= sculpt_brush_stroke_exec;
ot->poll= sculpt_poll;
/* flags (sculpt does own undo? (ton) */
ot->flag= OPTYPE_REGISTER|OPTYPE_BLOCKING;
/* properties */
RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
/* If the object has a scaling factor, brushes also need to be scaled
to work as expected. */
RNA_def_float_vector(ot->srna, "scale", 3, NULL, 0.0f, FLT_MAX, "Scale", "", 0.0f, 1000.0f);
RNA_def_int(ot->srna, "flag", 0, 0, INT_MAX, "flag", "", 0, INT_MAX);
/* For mirror modifiers */
RNA_def_float_vector(ot->srna, "clip_tolerance", 3, NULL, 0.0f, FLT_MAX, "clip_tolerance", "", 0.0f, 1000.0f);
/* The initial 2D location of the mouse */
RNA_def_float_vector(ot->srna, "initial_mouse", 2, NULL, INT_MIN, INT_MAX, "initial_mouse", "", INT_MIN, INT_MAX);
}
/**** Reset the copy of the mesh that is being sculpted on (currently just for the layer brush) ****/
static int sculpt_set_persistent_base(bContext *C, wmOperator *op)
{
SculptSession *ss = CTX_data_active_object(C)->sculpt;
if(ss) {
if(ss->layer_disps)
MEM_freeN(ss->layer_disps);
ss->layer_disps = NULL;
if(ss->layer_co)
MEM_freeN(ss->layer_co);
ss->layer_co = NULL;
}
return OPERATOR_FINISHED;
}
static void SCULPT_OT_set_persistent_base(wmOperatorType *ot)
{
/* identifiers */
ot->name= "Set Persistent Base";
ot->idname= "SCULPT_OT_set_persistent_base";
/* api callbacks */
ot->exec= sculpt_set_persistent_base;
ot->poll= sculpt_mode_poll;
ot->flag= OPTYPE_REGISTER;
}
/**** Toggle operator for turning sculpt mode on or off ****/
static void sculpt_init_session(Scene *scene, Object *ob)
{
ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session");
sculpt_update_mesh_elements(scene, ob, 0);
if(ob->sculpt->refkb)
sculpt_key_to_mesh(ob->sculpt->refkb, ob);
}
static int sculpt_toggle_mode(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = CTX_data_tool_settings(C);
Object *ob = CTX_data_active_object(C);
MultiresModifierData *mmd = sculpt_multires_active(ob);
if(ob->mode & OB_MODE_SCULPT) {
if(sculpt_multires_active(ob))
multires_force_update(ob);
if(mmd && mmd->sculptlvl != mmd->lvl)
DAG_id_flush_update(&ob->id, OB_RECALC_DATA);
/* Leave sculptmode */
ob->mode &= ~OB_MODE_SCULPT;
free_sculptsession(&ob->sculpt);
}
else {
/* Enter sculptmode */
ob->mode |= OB_MODE_SCULPT;
if(mmd && mmd->sculptlvl != mmd->lvl)
DAG_id_flush_update(&ob->id, OB_RECALC_DATA);
/* Create persistent sculpt mode data */
if(!ts->sculpt)
ts->sculpt = MEM_callocN(sizeof(Sculpt), "sculpt mode data");
/* Create sculpt mode session data */
if(ob->sculpt)
free_sculptsession(&ob->sculpt);
sculpt_init_session(scene, ob);
paint_init(&ts->sculpt->paint, PAINT_CURSOR_SCULPT);
paint_cursor_start(C, sculpt_poll);
}
WM_event_add_notifier(C, NC_SCENE|ND_MODE, CTX_data_scene(C));
return OPERATOR_FINISHED;
}
static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name= "Sculpt Mode";
ot->idname= "SCULPT_OT_sculptmode_toggle";
/* api callbacks */
ot->exec= sculpt_toggle_mode;
ot->poll= ED_operator_object_active;
ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
}
void ED_operatortypes_sculpt()
{
WM_operatortype_append(SCULPT_OT_radial_control);
WM_operatortype_append(SCULPT_OT_brush_stroke);
WM_operatortype_append(SCULPT_OT_sculptmode_toggle);
WM_operatortype_append(SCULPT_OT_set_persistent_base);
}