diff --git a/source/blender/editors/sculpt/sculpt.c b/source/blender/editors/sculpt/sculpt.c new file mode 100644 index 00000000000..0783c065ab9 --- /dev/null +++ b/source/blender/editors/sculpt/sculpt.c @@ -0,0 +1,2137 @@ +/* + * $Id: sculptmode.c 18309 2009-01-04 07:47:11Z nicholasbishop $ + * + * ***** 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 + * + * BDR_sculptmode.h + * + */ + +#include "GHOST_Types.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" +#include "BLI_dynstr.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_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_sculpt.h" +#include "BKE_texture.h" +#include "BKE_utildefines.h" +#include "BKE_colortools.h" + +#include "BIF_gl.h" +#include "BIF_glutil.h" + +#include "sculpt_intern.h" + +#include "IMB_imbuf_types.h" + +#include "RE_render_ext.h" +#include "RE_shader_ext.h" /*for multitex_ext*/ + +#include "GPU_draw.h" + +#include +#include +#include + +/* Number of vertices to average in order to determine the flatten distance */ +#define FLATTEN_SAMPLE_SIZE 10 + +/* Texture cache size */ +#define TC_SIZE 256 + +/* ===== STRUCTS ===== + * + */ + +/* ActiveData stores an Index into the mvert array of Mesh, plus Fade, which + stores how far the vertex is from the brush center, scaled to the range [0,1]. */ +typedef struct ActiveData { + struct ActiveData *next, *prev; + unsigned int Index; + float Fade; + float dist; +} ActiveData; + +typedef struct BrushActionSymm { + float center_3d[3]; + char index; + + float up[3], right[3], out[3]; + + /* Grab brush */ + float grab_delta[3]; +} BrushActionSymm; + +typedef struct BrushAction { + BrushActionSymm symm; + + char firsttime; + + /* Some brushes need access to original mesh vertices */ + vec3f *mesh_store; + short (*orig_norms)[3]; + + short mouse[2]; + float size_3d; + + float prev_radius; + float radius; + + float *layer_disps; + char flip; + + char clip[3]; + float cliptol[3]; + + float anchored_rot; + + /* Grab brush */ + ListBase grab_active_verts[8]; + float depth; + + /* Adjust brush strength along each axis + to adjust for object scaling */ + float scale[3]; +} BrushAction; + +typedef struct RectNode { + struct RectNode *next, *prev; + rcti r; +} RectNode; + +/* Used to store to 2D screen coordinates of each vertex in the mesh. */ +typedef struct ProjVert { + short co[2]; + + /* Used to mark whether a vertex is inside a rough bounding box + containing the brush. */ + char inside; +} ProjVert; + +static Object *active_ob= NULL; + +SculptData *sculpt_data(void) +{ + return NULL; /*XXX: return &G.scene->sculptdata; */ +} + +static void sculpt_init_session(void); +static void init_brushaction(BrushAction *a, short *, short *); +static void sculpt_undo_push(const short); + +SculptSession *sculpt_session(void) +{ + if(!sculpt_data()->session) + sculpt_init_session(); + return sculpt_data()->session; +} + +/* ===== MEMORY ===== + * + * Allocate/initialize/free data + */ + +static void sculpt_init_session(void) +{ + if(sculpt_data()->session) + ;/*XXX: sculptsession_free(G.scene); */ + sculpt_data()->session= MEM_callocN(sizeof(SculptSession), "SculptSession"); +} + +/* vertex_users is an array of Lists that store all the faces that use a + particular vertex. vertex_users is in the same order as mesh.mvert */ +static void calc_vertex_users() +{ + SculptSession *ss= sculpt_session(); + int i,j; + IndexNode *node= NULL; + + sculpt_vertexusers_free(ss); + + /* For efficiency, use vertex_users_mem as a memory pool (may be larger + than necessary if mesh has triangles, but only one alloc is needed.) */ + ss->vertex_users= MEM_callocN(sizeof(ListBase) * ss->totvert, "vertex_users"); + ss->vertex_users_size= ss->totvert; + ss->vertex_users_mem= MEM_callocN(sizeof(IndexNode)*ss->totface*4, "vertex_users_mem"); + node= ss->vertex_users_mem; + + /* Find the users */ + for(i=0; itotface; ++i){ + for(j=0; j<(ss->mface[i].v4?4:3); ++j, ++node) { + node->index=i; + BLI_addtail(&ss->vertex_users[((unsigned int*)(&ss->mface[i]))[j]], node); + } + } +} + +/* ===== INTERFACE ===== + */ + +/* XXX: this can probably removed entirely */ +#if 0 +void sculptmode_rem_tex(void *junk0,void *junk1) +{ + MTex *mtex= G.scene->sculptdata.mtex[G.scene->sculptdata.texact]; + if(mtex) { + SculptSession *ss= sculpt_session(); + if(mtex->tex) mtex->tex->id.us--; + MEM_freeN(mtex); + G.scene->sculptdata.mtex[G.scene->sculptdata.texact]= NULL; + /* Clear brush preview */ + if(ss->texcache) { + MEM_freeN(ss->texcache); + ss->texcache= NULL; + } + BIF_undo_push("Unlink brush texture"); + allqueue(REDRAWBUTSEDIT, 0); + allqueue(REDRAWOOPS, 0); + } +} +#endif + +/* ===== OPENGL ===== + * + * Simple functions to get data from the GL + */ + +/* Store the modelview and projection matrices and viewport. */ +void init_sculptmatrices() +{ + /* XXX: probably becomes context data? + + SculptSession *ss= sculpt_session(); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glMultMatrixf(OBACT->obmat); + + if(!ss->mats) + ss->mats = MEM_callocN(sizeof(bglMats), "sculpt bglmats"); + bgl_get_mats(ss->mats); + + glPopMatrix(); + */ +} + +/* Uses window coordinates (x,y) to find the depth in the GL depth buffer. If + available, G.vd->depths is used so that the brush doesn't sculpt on top of + itself (G.vd->depths is only updated at the end of a brush stroke.) */ +float get_depth(short x, short y) +{ + /* XXX + float depth; + + if(x<0 || y<0) return 1; + if(x>=curarea->winx || y>=curarea->winy) return 1; + + if(G.vd->depths && xdepths->w && ydepths->h) + return G.vd->depths->depths[y*G.vd->depths->w+x]; + + x+= curarea->winrct.xmin; + y+= curarea->winrct.ymin; + + glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); + + return depth; */ + return 0; +} + +/* Uses window coordinates (x,y) and depth component z to find a point in + modelspace */ +void unproject(float out[3], const short x, const short y, const float z) +{ + SculptSession *ss= sculpt_session(); + double ux, uy, uz; + + gluUnProject(x,y,z, ss->mats->modelview, ss->mats->projection, + (GLint *)ss->mats->viewport, &ux, &uy, &uz ); + out[0] = ux; + out[1] = uy; + out[2] = uz; +} + +/* Convert a point in model coordinates to 2D screen coordinates. */ +static void projectf(const float v[3], float p[2]) +{ + SculptSession *ss= sculpt_session(); + double ux, uy, uz; + + gluProject(v[0],v[1],v[2], ss->mats->modelview, ss->mats->projection, + (GLint *)ss->mats->viewport, &ux, &uy, &uz); + p[0]= ux; + p[1]= uy; +} + +static void project(const float v[3], short p[2]) +{ + float f[2]; + projectf(v, f); + + p[0]= f[0]; + p[1]= f[1]; +} + +/* ===== Sculpting ===== + * + */ + +/* Return modified brush size. Uses current tablet pressure (if available) to + shrink the brush. Skipped for grab brush because only the first mouse down + size is used, which is small if the user has just touched the pen to the + tablet */ +char brush_size() +{ + SculptData *sd = NULL; /* XXX */ + const BrushData *b= sculptmode_brush(); + float size= b->size; + float pressure= get_pressure(); + short activedevice= get_activedevice(); + + if(sculpt_data()->brush_type!=GRAB_BRUSH) { + const float size_factor= sd->tablet_size / 10.0f; + + /* XXX: tablet stuff + if(ELEM(activedevice, DEV_STYLUS, DEV_ERASER)) + size*= sd->tablet_size==0?1: + (1-size_factor) + pressure*size_factor;*/ + } + + return size; +} + +/* 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. */ +float brush_strength(BrushAction *a) +{ + SculptData *sd = NULL; /* XXX */ + const BrushData* b= sculptmode_brush(); + float dir= b->dir==1 ? 1 : -1; + float pressure= 1; + short activedevice= get_activedevice(); + float flip= a->flip ? -1:1; + float anchored = b->flag & SCULPT_BRUSH_ANCHORED ? 25 : 1; + + const float strength_factor= sd->tablet_strength / 10.0f; + + /* XXX: tablet stuff */ +#if 0 + if(ELEM(activedevice, DEV_STYLUS, DEV_ERASER)) + pressure= sd->sculptdata.tablet_strength==0?1: + (1-strength_factor) + get_pressure()*strength_factor; + + /* Flip direction for eraser */ + if(activedevice==DEV_ERASER) + dir= -dir; +#endif + + switch(sd->brush_type){ + case DRAW_BRUSH: + case LAYER_BRUSH: + return b->strength / 5000.0f * dir * pressure * flip * anchored; /*XXX: not sure why? multiplied by G.vd->grid */; + case SMOOTH_BRUSH: + return b->strength / 50.0f * pressure * anchored; + case PINCH_BRUSH: + return b->strength / 1000.0f * dir * pressure * flip * anchored; + case GRAB_BRUSH: + return 1; + case INFLATE_BRUSH: + return b->strength / 5000.0f * dir * pressure * flip * anchored; + case FLATTEN_BRUSH: + return b->strength / 500.0f * pressure * anchored; + default: + return 0; + } +} + +/* For clipping against a mirror modifier */ +void sculpt_clip(const BrushAction *a, float *co, const float val[3]) +{ + char i; + for(i=0; i<3; ++i) { + if(a->clip[i] && (fabs(co[i]) <= a->cliptol[i])) + co[i]= 0.0f; + else + co[i]= val[i]; + } +} + +void sculpt_axislock(float *co) +{ + SculptData *sd = sculpt_data(); + if (sd->axislock == AXISLOCK_X+AXISLOCK_Y+AXISLOCK_Z) return; + /* XXX: if(G.vd->twmode == V3D_MANIP_LOCAL) { */ + if(0) { + float mat[3][3], imat[3][3]; + /* XXX: Mat3CpyMat4(mat, OBACT->obmat); */ + Mat3Inv(imat, mat); + Mat3MulVecfl(mat, co); + if (sd->axislock & AXISLOCK_X) co[0] = 0.0; + if (sd->axislock & AXISLOCK_Y) co[1] = 0.0; + if (sd->axislock & AXISLOCK_Z) co[2] = 0.0; + Mat3MulVecfl(imat, co); + } else { + if (sd->axislock & AXISLOCK_X) co[0] = 0.0; + if (sd->axislock & AXISLOCK_Y) co[1] = 0.0; + if (sd->axislock & AXISLOCK_Z) co[2] = 0.0; + } +} + +static void add_norm_if(float view_vec[3], float out[3], float out_flip[3], const short no[3]) +{ + float fno[3] = {no[0], no[1], no[2]}; + + Normalize(fno); + + if((Inpf(view_vec, fno)) > 0) { + VecAddf(out, out, fno); + } else { + VecAddf(out_flip, out_flip, fno); /* out_flip is used when out is {0,0,0} */ + } +} + +/* Currently only for the draw brush; finds average normal for all active + vertices */ +void calc_area_normal(float out[3], const BrushAction *a, const float *outdir, const ListBase* active_verts) +{ + ActiveData *node = active_verts->first; + SculptData *sd = sculpt_data(); + const int view = sd->brush_type==DRAW_BRUSH ? sculptmode_brush()->view : 0; + float out_flip[3]; + + out[0]=out[1]=out[2] = out_flip[0]=out_flip[1]=out_flip[2] = 0; + + if(sculptmode_brush()->flag & SCULPT_BRUSH_ANCHORED) { + for(; node; node = node->next) + add_norm_if(((BrushAction*)a)->symm.out, out, out_flip, a->orig_norms[node->Index]); + } + else { + for(; node; node = node->next) + add_norm_if(((BrushAction*)a)->symm.out, out, out_flip, sd->session->mvert[node->Index].no); + } + + if (out[0]==0.0 && out[1]==0.0 && out[2]==0.0) { + VECCOPY(out, out_flip); + } + + Normalize(out); + + if(outdir) { + out[0] = outdir[0] * view + out[0] * (10-view); + out[1] = outdir[1] * view + out[1] * (10-view); + out[2] = outdir[2] * view + out[2] * (10-view); + } + + Normalize(out); +} + +void do_draw_brush(SculptSession *ss, const BrushAction *a, const ListBase* active_verts) +{ + float area_normal[3]; + ActiveData *node= active_verts->first; + + calc_area_normal(area_normal, a, a->symm.out, active_verts); + + sculpt_axislock(area_normal); + + while(node){ + float *co= ss->mvert[node->Index].co; + + const float val[3]= {co[0]+area_normal[0]*node->Fade*a->scale[0], + co[1]+area_normal[1]*node->Fade*a->scale[1], + co[2]+area_normal[2]*node->Fade*a->scale[2]}; + + sculpt_clip(a, co, val); + + node= node->next; + } +} + +/* 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.) */ +vec3f neighbor_average(SculptSession *ss, const int vert) +{ + int i, skip= -1, total=0; + IndexNode *node= ss->vertex_users[vert].first; + vec3f avg= {0,0,0}; + char ncount= BLI_countlist(&ss->vertex_users[vert]); + MFace *f; + + /* Don't modify corner vertices */ + if(ncount==1) { + VecCopyf(&avg.x, ss->mvert[vert].co); + return avg; + } + + 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->vertex_users[(&f->v1)[i]]) <= 2)) { + VecAddf(&avg.x, &avg.x, ss->mvert[(&f->v1)[i]].co); + ++total; + } + } + + node= node->next; + } + + if(total>0) { + avg.x/= total; + avg.y/= total; + avg.z/= total; + } + else + VecCopyf(&avg.x, ss->mvert[vert].co); + + return avg; +} + +void do_smooth_brush(SculptSession *ss, const BrushAction *a, const ListBase* active_verts) +{ + ActiveData *node= active_verts->first; + + while(node){ + float *co= ss->mvert[node->Index].co; + const vec3f avg= neighbor_average(ss, node->Index); + const float val[3]= {co[0]+(avg.x-co[0])*node->Fade, + co[1]+(avg.y-co[1])*node->Fade, + co[2]+(avg.z-co[2])*node->Fade}; + sculpt_clip(a, co, val); + node= node->next; + } +} + +void do_pinch_brush(SculptSession *ss, const BrushAction *a, const ListBase* active_verts) +{ + ActiveData *node= active_verts->first; + + while(node) { + float *co= ss->mvert[node->Index].co; + const float val[3]= {co[0]+(a->symm.center_3d[0]-co[0])*node->Fade, + co[1]+(a->symm.center_3d[1]-co[1])*node->Fade, + co[2]+(a->symm.center_3d[2]-co[2])*node->Fade}; + sculpt_clip(a, co, val); + node= node->next; + } +} + +void do_grab_brush(SculptSession *ss, BrushAction *a) +{ + ActiveData *node= a->grab_active_verts[a->symm.index].first; + float add[3]; + float grab_delta[3]; + + VecCopyf(grab_delta, a->symm.grab_delta); + sculpt_axislock(grab_delta); + + while(node) { + float *co= ss->mvert[node->Index].co; + + VecCopyf(add, grab_delta); + VecMulf(add, node->Fade); + VecAddf(add, add, co); + sculpt_clip(a, co, add); + + node= node->next; + } + +} + +void do_layer_brush(SculptSession *ss, BrushAction *a, const ListBase *active_verts) +{ + float area_normal[3]; + ActiveData *node= active_verts->first; + const float bstr= brush_strength(a); + + calc_area_normal(area_normal, a, NULL, active_verts); + + while(node){ + float *disp= &a->layer_disps[node->Index]; + + if((bstr > 0 && *disp < bstr) || + (bstr < 0 && *disp > bstr)) { + float *co= ss->mvert[node->Index].co; + + *disp+= node->Fade; + + if(bstr < 0) { + if(*disp < bstr) + *disp = bstr; + } else { + if(*disp > bstr) + *disp = bstr; + } + + { + const float val[3]= {a->mesh_store[node->Index].x+area_normal[0] * *disp*a->scale[0], + a->mesh_store[node->Index].y+area_normal[1] * *disp*a->scale[1], + a->mesh_store[node->Index].z+area_normal[2] * *disp*a->scale[2]}; + sculpt_clip(a, co, val); + } + } + + node= node->next; + } +} + +void do_inflate_brush(SculptSession *ss, const BrushAction *a, const ListBase *active_verts) +{ + ActiveData *node= active_verts->first; + float add[3]; + + while(node) { + float *co= ss->mvert[node->Index].co; + short *no= ss->mvert[node->Index].no; + + add[0]= no[0]/ 32767.0f; + add[1]= no[1]/ 32767.0f; + add[2]= no[2]/ 32767.0f; + VecMulf(add, node->Fade); + add[0]*= a->scale[0]; + add[1]*= a->scale[1]; + add[2]*= a->scale[2]; + VecAddf(add, add, co); + + sculpt_clip(a, co, add); + + node= node->next; + } +} + +void calc_flatten_center(SculptSession *ss, ActiveData *node, float co[3]) +{ + ActiveData *outer[FLATTEN_SAMPLE_SIZE]; + int i; + + for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i) + outer[i] = node; + + for(; node; node = node->next) { + for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i) { + if(node->dist > outer[i]->dist) { + outer[i] = node; + break; + } + } + } + + co[0] = co[1] = co[2] = 0.0f; + for(i = 0; i < FLATTEN_SAMPLE_SIZE; ++i) + VecAddf(co, co, ss->mvert[outer[i]->Index].co); + VecMulf(co, 1.0f / FLATTEN_SAMPLE_SIZE); +} + +void do_flatten_brush(SculptSession *ss, const BrushAction *a, const ListBase *active_verts) +{ + ActiveData *node= active_verts->first; + /* area_normal and cntr define the plane towards which vertices are squashed */ + float area_normal[3]; + float cntr[3]; + + calc_area_normal(area_normal, a, a->symm.out, active_verts); + calc_flatten_center(ss, node, cntr); + + while(node){ + float *co= ss->mvert[node->Index].co; + float p1[3], sub1[3], sub2[3], intr[3], val[3]; + + /* Find the intersection between squash-plane and vertex (along the area normal) */ + VecSubf(p1, co, area_normal); + VecSubf(sub1, cntr, p1); + VecSubf(sub2, co, p1); + VecSubf(intr, co, p1); + VecMulf(intr, Inpf(area_normal, sub1) / Inpf(area_normal, sub2)); + VecAddf(intr, intr, p1); + + VecSubf(val, intr, co); + VecMulf(val, node->Fade); + VecAddf(val, val, co); + + sculpt_clip(a, co, val); + + node= node->next; + } +} + +/* Uses the brush curve control to find a strength value between 0 and 1 */ +float curve_strength(float p, const float len) +{ + SculptData *sd = NULL; /* XXX */ + if(p > len) p= len; + return curvemapping_evaluateF(sd->cumap, 0, p/len); +} + +/* Uses symm to selectively flip any axis of a coordinate. */ +void flip_coord(float co[3], const char symm) +{ + if(symm & SYMM_X) + co[0]= -co[0]; + if(symm & SYMM_Y) + co[1]= -co[1]; + if(symm & SYMM_Z) + co[2]= -co[2]; +} + +/* Use the warpfac field in MTex to store a rotation value for sculpt textures. Value is in degrees */ +float sculpt_tex_angle(void) +{ + SculptData *sd= sculpt_data(); + if(sd->texact!=-1 && sd->mtex[sd->texact]) + return sd->mtex[sd->texact]->warpfac; + return 0; +} + +void set_tex_angle(const float f) +{ + SculptData *sd = sculpt_data(); + if(sd->texact != -1 && sd->mtex[sd->texact]) + sd->mtex[sd->texact]->warpfac = f; +} + +float to_rad(const float deg) +{ + return deg * (M_PI/180.0f); +} + +float to_deg(const float rad) +{ + return rad * (180.0f/M_PI); +} + +/* 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_w + 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 = TC_SIZE - 1; + float urat, vrat, uopp; + + if(u < 0) u = 0; + else if(u >= TC_SIZE) u = tc_max; + if(v < 0) v = 0; + else if(v >= TC_SIZE) v = tc_max; + + x = floor(u); + y = floor(v); + x2 = x + 1; + y2 = y + 1; + + if(x2 > TC_SIZE) x2 = tc_max; + if(y2 > TC_SIZE) 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. */ +float tex_strength(BrushAction *a, float *point, const float len,const unsigned vindex) +{ + SculptData *sd= sculpt_data(); + SculptSession *ss= sculpt_session(); + float avg= 1; + + if(sd->texact==-1 || !sd->mtex[sd->texact]) + avg= 1; + else if(sd->texrept==SCULPTREPT_3D) { + /* Get strength by feeding the vertex location directly + into a texture */ + float jnk; + const float factor= 0.01; + MTex mtex; + memset(&mtex,0,sizeof(MTex)); + mtex.tex= sd->mtex[sd->texact]->tex; + mtex.projx= 1; + mtex.projy= 2; + mtex.projz= 3; + VecCopyf(mtex.size, sd->mtex[sd->texact]->size); + VecMulf(mtex.size, factor); + if(!sd->texsep) + mtex.size[1]= mtex.size[2]= mtex.size[0]; + + externtex(&mtex,point,&avg,&jnk,&jnk,&jnk,&jnk); + } + else if(ss->texcache) { + const float bsize= a->radius * 2; + const float rot= to_rad(sculpt_tex_angle()) + a->anchored_rot; + 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. */ + VecCopyf(flip, point); + flip_coord(flip, a->symm.index); + projectf(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(sd->texrept==SCULPTREPT_TILE) { + const int sx= (const int)sd->mtex[sd->texact]->size[0]; + const int sy= (const int)sd->texsep ? sd->mtex[sd->texact]->size[1] : sx; + + 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, TC_SIZE*px/sx, TC_SIZE*py/sy); + } else { + float fx= (point_2d[0] - a->mouse[0]) / bsize; + float fy= (point_2d[1] - a->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 * TC_SIZE, fy * TC_SIZE); + } + } + + if(sd->texfade) + avg*= curve_strength(len, a->size_3d); /* Smooth curve */ + + return avg; +} + +/* Mark area around the brush as damaged. projverts are marked if they are + inside the area and the damaged rectangle in 2D screen coordinates is + added to damaged_rects. */ +void sculpt_add_damaged_rect(BrushAction *a) +{ + short p[2]; + RectNode *rn= MEM_mallocN(sizeof(RectNode),"RectNode"); + SculptSession *ss = sculpt_session(); + const float radius = a->radius > a->prev_radius ? a->radius : a->prev_radius; + unsigned i; + + /* Find center */ + project(a->symm.center_3d, p); + rn->r.xmin= p[0] - radius; + rn->r.ymin= p[1] - radius; + rn->r.xmax= p[0] + radius; + rn->r.ymax= p[1] + radius; + + BLI_addtail(&sculpt_session()->damaged_rects, rn); + + /* Update insides */ + for(i=0; itotvert; ++i) { + if(!ss->projverts[i].inside) { + if(ss->projverts[i].co[0] > rn->r.xmin && ss->projverts[i].co[1] > rn->r.ymin && + ss->projverts[i].co[0] < rn->r.xmax && ss->projverts[i].co[1] < rn->r.ymax) { + ss->projverts[i].inside= 1; + } + } + } +} + +/* Clears the depth buffer in each modified area. */ +void sculpt_clear_damaged_areas(SculptSession *ss) +{ + RectNode *rn= NULL; + + for(rn = ss->damaged_rects.first; rn; rn = rn->next) { + rcti clp = rn->r; + rcti *win = NULL; /*XXX: &curarea->winrct; */ + + clp.xmin += win->xmin; + clp.xmax += win->xmin; + clp.ymin += win->ymin; + clp.ymax += win->ymin; + + if(clp.xmin < win->xmax && clp.xmax > win->xmin && + clp.ymin < win->ymax && clp.ymax > win->ymin) { + if(clp.xmin < win->xmin) clp.xmin = win->xmin; + if(clp.ymin < win->ymin) clp.ymin = win->ymin; + if(clp.xmax > win->xmax) clp.xmax = win->xmax; + if(clp.ymax > win->ymax) clp.ymax = win->ymax; + + glScissor(clp.xmin + 1, clp.ymin + 1, + clp.xmax - clp.xmin - 2, + clp.ymax - clp.ymin - 2); + } + + glClear(GL_DEPTH_BUFFER_BIT); + } +} + +void do_brush_action(BrushAction *a) +{ + int i; + float av_dist; + ListBase active_verts={0,0}; + ActiveData *adata= 0; + float *vert; + Mesh *me= NULL; /*XXX: get_mesh(OBACT); */ + const float bstrength= brush_strength(a); + KeyBlock *keyblock= NULL; /*XXX: ob_get_keyblock(OBACT); */ + SculptData *sd = sculpt_data(); + SculptSession *ss = sculpt_session(); + + sculpt_add_damaged_rect(a); + + /* Build a list of all vertices that are potentially within the brush's + area of influence. Only do this once for the grab brush. */ + if((sd->brush_type != GRAB_BRUSH) || a->firsttime) { + for(i=0; itotvert; ++i) { + /* Projverts.inside provides a rough bounding box */ + if(ss->multires || ss->projverts[i].inside) { + //vert= ss->vertexcosnos ? &ss->vertexcosnos[i*6] : a->verts[i].co; + vert= ss->mvert[i].co; + av_dist= VecLenf(a->symm.center_3d, vert); + if(av_dist < a->size_3d) { + adata= (ActiveData*)MEM_mallocN(sizeof(ActiveData), "ActiveData"); + + adata->Index = i; + /* Fade is used to store the final strength at which the brush + should modify a particular vertex. */ + adata->Fade= tex_strength(a, vert, av_dist, i) * bstrength; + adata->dist = av_dist; + + if(sd->brush_type == GRAB_BRUSH && a->firsttime) + BLI_addtail(&a->grab_active_verts[a->symm.index], adata); + else + BLI_addtail(&active_verts, adata); + } + } + } + } + + /* Only act if some verts are inside the brush area */ + if(active_verts.first || (sd->brush_type == GRAB_BRUSH && a->grab_active_verts[a->symm.index].first)) { + /* Apply one type of brush action */ + switch(sd->brush_type){ + case DRAW_BRUSH: + do_draw_brush(ss, a, &active_verts); + break; + case SMOOTH_BRUSH: + do_smooth_brush(ss, a, &active_verts); + break; + case PINCH_BRUSH: + do_pinch_brush(ss, a, &active_verts); + break; + case INFLATE_BRUSH: + do_inflate_brush(ss, a, &active_verts); + break; + case GRAB_BRUSH: + do_grab_brush(ss, a); + break; + case LAYER_BRUSH: + do_layer_brush(ss, a, &active_verts); + break; + case FLATTEN_BRUSH: + do_flatten_brush(ss, a, &active_verts); + break; + } + + /* Copy the modified vertices from mesh to the active key */ + if(keyblock && !ss->multires) { + float *co= keyblock->data; + if(co) { + if(sd->brush_type == GRAB_BRUSH) + adata = a->grab_active_verts[a->symm.index].first; + else + adata = active_verts.first; + + for(; adata; adata= adata->next) + if(adata->Index < keyblock->totelem) + VecCopyf(&co[adata->Index*3], me->mvert[adata->Index].co); + } + } + + if(ss->vertexcosnos && !ss->multires) + BLI_freelistN(&active_verts); + else { + if(sd->brush_type != GRAB_BRUSH) + addlisttolist(&ss->damaged_verts, &active_verts); + } + } +} + +/* Flip all the editdata across the axis/axes specified by symm. Used to + calculate multiple modifications to the mesh when symmetry is enabled. */ +void calc_brushdata_symm(BrushAction *a, const char symm) +{ + flip_coord(a->symm.center_3d, symm); + flip_coord(a->symm.up, symm); + flip_coord(a->symm.right, symm); + flip_coord(a->symm.out, symm); + + a->symm.index= symm; + + flip_coord(a->symm.grab_delta, symm); +} + +void do_symmetrical_brush_actions(BrushAction *a, short co[2], short pr_co[2]) +{ + const char symm = sculpt_data()->symm; + BrushActionSymm orig; + int i; + + init_brushaction(a, co, pr_co); + orig = a->symm; + do_brush_action(a); + + for(i = 1; i <= symm; ++i) { + if(symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5))) { + // Restore the original symmetry data + a->symm = orig; + + calc_brushdata_symm(a, i); + do_brush_action(a); + } + } + + a->symm = orig; +} + +void add_face_normal(vec3f *norm, MVert *mvert, const MFace* face, float *fn) +{ + vec3f c= {mvert[face->v1].co[0],mvert[face->v1].co[1],mvert[face->v1].co[2]}; + vec3f b= {mvert[face->v2].co[0],mvert[face->v2].co[1],mvert[face->v2].co[2]}; + vec3f a= {mvert[face->v3].co[0],mvert[face->v3].co[1],mvert[face->v3].co[2]}; + vec3f s1, s2; + float final[3]; + + VecSubf(&s1.x,&a.x,&b.x); + VecSubf(&s2.x,&c.x,&b.x); + + final[0] = s1.y * s2.z - s1.z * s2.y; + final[1] = s1.z * s2.x - s1.x * s2.z; + final[2] = s1.x * s2.y - s1.y * s2.x; + + if(fn) + VecCopyf(fn, final); + + norm->x+= final[0]; + norm->y+= final[1]; + norm->z+= final[2]; +} + +void update_damaged_vert(ListBase *lb, BrushAction *a) +{ + ActiveData *vert; + SculptSession *ss = sculpt_session(); + + for(vert= lb->first; vert; vert= vert->next) { + vec3f norm= {0,0,0}; + IndexNode *face= sculpt_session()->vertex_users[vert->Index].first; + + while(face){ + float *fn = NULL; + if(ss->face_normals) + fn = &ss->face_normals[face->index*3]; + add_face_normal(&norm, ss->mvert, &ss->mface[face->index], fn); + face= face->next; + } + Normalize(&norm.x); + + ss->mvert[vert->Index].no[0]=norm.x*32767; + ss->mvert[vert->Index].no[1]=norm.y*32767; + ss->mvert[vert->Index].no[2]=norm.z*32767; + } +} + +void calc_damaged_verts(ListBase *damaged_verts, BrushAction *a) +{ + int i; + + for(i=0; i<8; ++i) + update_damaged_vert(&a->grab_active_verts[i], a); + + update_damaged_vert(damaged_verts, a); + BLI_freelistN(damaged_verts); + damaged_verts->first = damaged_verts->last = NULL; +} + +void projverts_clear_inside(SculptSession *ss) +{ + int i; + for(i = 0; i < ss->totvert; ++i) + ss->projverts[i].inside = 0; +} + +BrushData *sculptmode_brush(void) +{ + SculptData *sd= sculpt_data(); + + BrushData *bd = + (sd->brush_type==DRAW_BRUSH ? &sd->drawbrush : + sd->brush_type==SMOOTH_BRUSH ? &sd->smoothbrush : + sd->brush_type==PINCH_BRUSH ? &sd->pinchbrush : + sd->brush_type==INFLATE_BRUSH ? &sd->inflatebrush : + sd->brush_type==GRAB_BRUSH ? &sd->grabbrush : + sd->brush_type==LAYER_BRUSH ? &sd->layerbrush : + sd->brush_type==FLATTEN_BRUSH ? &sd->flattenbrush : NULL); + + if(!bd) { + /* XXX: sculptdata_init(G.scene); */ + bd = &sd->drawbrush; + } + + return bd; +} + +void sculptmode_update_tex() +{ + SculptData *sd= sculpt_data(); + SculptSession *ss= sculpt_session(); + MTex *mtex = sd->mtex[sd->texact]; + TexResult texres = {0}; + float x, y, step=2.0/TC_SIZE, co[3]; + int hasrgb, ix, iy; + + /* Skip Default brush shape and non-textures */ + if(sd->texact == -1 || !sd->mtex[sd->texact]) return; + + if(ss->texcache) { + MEM_freeN(ss->texcache); + ss->texcache= NULL; + } + + ss->texcache_w = ss->texcache_h = TC_SIZE; + ss->texcache = MEM_callocN(sizeof(int) * ss->texcache_w * ss->texcache_h, "Sculpt Texture cache"); + + if(mtex && mtex->tex) { + BKE_image_get_ibuf(sd->mtex[sd->texact]->tex->ima, NULL); + + /*do normalized cannonical view coords for texture*/ + for (y=-1.0, iy=0; iytex, co, NULL, NULL, 1, &texres); + + /* if the texture gave an RGB value, we assume it didn't give a valid + * intensity, so calculate one (formula from do_material_tex). + * if the texture didn't give an RGB value, copy the intensity across + */ + if(hasrgb & TEX_RGB) + texres.tin = (0.35 * texres.tr + 0.45 * + texres.tg + 0.2 * texres.tb); + + texres.tin = texres.tin * 255.0; + ((char*)ss->texcache)[(iy*TC_SIZE+ix)*4] = (char)texres.tin; + ((char*)ss->texcache)[(iy*TC_SIZE+ix)*4+1] = (char)texres.tin; + ((char*)ss->texcache)[(iy*TC_SIZE+ix)*4+2] = (char)texres.tin; + ((char*)ss->texcache)[(iy*TC_SIZE+ix)*4+3] = (char)texres.tin; + } + } + } +} + +/* pr_mouse is only used for the grab brush, can be NULL otherwise */ +static void init_brushaction(BrushAction *a, short *mouse, short *pr_mouse) +{ + SculptData *sd = sculpt_data(); + SculptSession *ss = sculpt_session(); + Object *ob = NULL; /* XXX */ + const float mouse_depth = get_depth(mouse[0], mouse[1]); + float brush_edge_loc[3], zero_loc[3], oldloc[3]; + ModifierData *md; + int i; + const char flip = 0; /*XXX: (get_qual() == LR_SHIFTKEY); */ + const char anchored = sculptmode_brush()->flag & SCULPT_BRUSH_ANCHORED; + short orig_mouse[2], dx=0, dy=0; + + a->flip = flip; + a->symm.index = 0; + + if(a->firsttime) + a->depth = mouse_depth; + + /* Convert the location and size of the brush to + modelspace coords */ + if(a->firsttime || !anchored) { + unproject(a->symm.center_3d, mouse[0], mouse[1], mouse_depth); + a->mouse[0] = mouse[0]; + a->mouse[1] = mouse[1]; + } + + if(anchored) { + project(a->symm.center_3d, orig_mouse); + dx = mouse[0] - orig_mouse[0]; + dy = mouse[1] - orig_mouse[1]; + } + + if(anchored) { + unproject(brush_edge_loc, mouse[0], mouse[1], a->depth); + a->anchored_rot = atan2(dy, dx); + } + else + unproject(brush_edge_loc, mouse[0] + brush_size(), mouse[1], mouse_depth); + + a->size_3d = VecLenf(a->symm.center_3d, brush_edge_loc); + + a->prev_radius = a->radius; + + if(anchored) + a->radius = sqrt(dx*dx + dy*dy); + else + a->radius = brush_size(); + + /* Set the pivot to allow the model to rotate around the center of the brush */ + if(get_depth(mouse[0],mouse[1]) < 1.0) + VecCopyf(sd->pivot, a->symm.center_3d); + + /* Now project the Up, Right, and Out normals from view to model coords */ + unproject(zero_loc, 0, 0, 0); + unproject(a->symm.up, 0, -1, 0); + unproject(a->symm.right, 1, 0, 0); + unproject(a->symm.out, 0, 0, -1); + VecSubf(a->symm.up, a->symm.up, zero_loc); + VecSubf(a->symm.right, a->symm.right, zero_loc); + VecSubf(a->symm.out, a->symm.out, zero_loc); + Normalize(a->symm.up); + Normalize(a->symm.right); + Normalize(a->symm.out); + + /* Initialize mirror modifier clipping */ + for(i=0; i<3; ++i) { + a->clip[i]= 0; + a->cliptol[i]= 0; + } + for(md= ob->modifiers.first; md; md= md->next) { + if(md->type==eModifierType_Mirror && (md->mode & eModifierMode_Realtime)) { + const MirrorModifierData *mmd = (MirrorModifierData*) md; + + if(mmd->flag & MOD_MIR_CLIPPING) { + a->clip[mmd->axis]= 1; + if(mmd->tolerance > a->cliptol[mmd->axis]) + a->cliptol[mmd->axis] = mmd->tolerance; + } + } + } + + if(sd->brush_type == GRAB_BRUSH) { + float gcenter[3]; + + /* Find the delta */ + unproject(gcenter, mouse[0], mouse[1], a->depth); + unproject(oldloc, pr_mouse[0], pr_mouse[1], a->depth); + VecSubf(a->symm.grab_delta, gcenter, oldloc); + } + else if(sd->brush_type == LAYER_BRUSH) { + if(!a->layer_disps) + a->layer_disps= MEM_callocN(sizeof(float)*ss->totvert,"Layer disps"); + } + + if(sd->brush_type == LAYER_BRUSH || anchored) { + unsigned i; + + if(!a->mesh_store) { + a->mesh_store= MEM_mallocN(sizeof(vec3f) * ss->totvert, "Sculpt mesh store"); + for(i = 0; i < ss->totvert; ++i) + VecCopyf(&a->mesh_store[i].x, ss->mvert[i].co); + } + + if(anchored && a->layer_disps) + memset(a->layer_disps, 0, sizeof(float) * ss->totvert); + + if(anchored && !a->orig_norms) { + a->orig_norms= MEM_mallocN(sizeof(short) * 3 * ss->totvert, "Sculpt orig norm"); + for(i = 0; i < ss->totvert; ++i) { + a->orig_norms[i][0] = ss->mvert[i].no[0]; + a->orig_norms[i][1] = ss->mvert[i].no[1]; + a->orig_norms[i][2] = ss->mvert[i].no[2]; + } + } + } +} +void sculptmode_set_strength(const int delta) +{ + int val = sculptmode_brush()->strength + delta; + if(val < 1) val = 1; + if(val > 100) val = 100; + sculptmode_brush()->strength= val; +} + +/* XXX: haven't brought in the radial control files, not sure where to put them. Note that all the paint modes should have access to radial control! */ +#if 0 +static void sculpt_radialcontrol_callback(const int mode, const int val) +{ + SculptSession *ss = sculpt_session(); + BrushData *br = sculptmode_brush(); + + if(mode == RADIALCONTROL_SIZE) + br->size = val; + else if(mode == RADIALCONTROL_STRENGTH) + br->strength = val; + else if(mode == RADIALCONTROL_ROTATION) + set_tex_angle(val); + + ss->radialcontrol = NULL; +} + +/* Returns GL handle to brush texture */ +static GLuint sculpt_radialcontrol_calctex() +{ + SculptData *sd= sculpt_data(); + SculptSession *ss= sculpt_session(); + int i, j; + const int tsz = TC_SIZE; + float *texdata= MEM_mallocN(sizeof(float)*tsz*tsz, "Brush preview"); + GLuint tex; + + if(sd->texrept!=SCULPTREPT_3D) + sculptmode_update_tex(); + for(i=0; itexfade) + texdata[i*tsz+j]= curve_strength(magn,tsz/2); + else + texdata[i*tsz+j]= magn < tsz/2 ? 1 : 0; + } + if(sd->texact != -1 && ss->texcache) { + for(i=0; itexcache[i*tsz+j]; + texdata[i*tsz+j]*= (((char*)&col)[0]+((char*)&col)[1]+((char*)&col)[2])/3.0f/255.0f; + } + } + + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, tsz, tsz, 0, GL_ALPHA, GL_FLOAT, texdata); + MEM_freeN(texdata); + + return tex; +} + +void sculpt_radialcontrol_start(int mode) +{ + SculptData *sd = sculpt_data(); + SculptSession *ss = sculpt_session(); + BrushData *br = sculptmode_brush(); + int orig=1, max=100; + + if(mode == RADIALCONTROL_SIZE) { + orig = br->size; + max = 200; + } + else if(mode == RADIALCONTROL_STRENGTH) { + orig = br->strength; + max = 100; + } + else if(mode == RADIALCONTROL_ROTATION) { + if(sd->texact!=-1 && sd->mtex[sd->texact]) { + orig = sculpt_tex_angle(); + max = 360; + } + else + mode = RADIALCONTROL_NONE; + } + + if(mode != RADIALCONTROL_NONE) { + ss->radialcontrol= radialcontrol_start(mode, sculpt_radialcontrol_callback, orig, max, + sculpt_radialcontrol_calctex()); + } +} +#endif + +/* XXX: drawing code to go elsewhere! +void sculpt_paint_brush(char clear) +{ + if(sculpt_data()->flags & SCULPT_DRAW_BRUSH) { + static short mvalo[2]; + short mval[2]; + const short rad= sculptmode_brush()->size; + + getmouseco_areawin(mval); + + persp(PERSP_WIN); + if(clear) + fdrawXORcirc(mval[0], mval[1], rad); + else + draw_sel_circle(mval, mvalo, rad, rad, 0); + + mvalo[0]= mval[0]; + mvalo[1]= mval[1]; + } +} +*/ + +void sculptmode_selectbrush_menu(void) +{ + /* XXX: I guess menus belong elsewhere too? + + SculptData *sd= sculpt_data(); + int val; + + pupmenu_set_active(sd->brush_type); + + val= pupmenu("Select Brush%t|Draw|Smooth|Pinch|Inflate|Grab|Layer|Flatten"); + + if(val>0) { + sd->brush_type= val; + + BIF_undo_push("Brush type"); + + allqueue(REDRAWVIEW3D, 1); + allqueue(REDRAWBUTSEDIT, 1); + }*/ +} + +void sculptmode_update_all_projverts(float *vertcosnos) +{ + SculptSession *ss = sculpt_session(); + unsigned i; + + if(!ss->projverts) + ss->projverts = MEM_mallocN(sizeof(ProjVert)*ss->totvert,"ProjVerts"); + + for(i=0; itotvert; ++i) { + project(vertcosnos ? &vertcosnos[i * 6] : ss->mvert[i].co, ss->projverts[i].co); + ss->projverts[i].inside= 0; + } +} + +/* 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(md->mode & eModifierMode_Realtime && md->type != eModifierType_Multires) + 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; + + for(md= modifiers_getVirtualModifierList(ob); md; md= md->next) { + if(md->type == eModifierType_Multires && !md->next) { + MultiresModifierData *mmd = (MultiresModifierData*)md; + if(mmd->lvl != 1) + return mmd; + } + } + + return NULL; +} + +static void sculpt_update_mesh_elements(SculptSession *ss, Object *ob) +{ + if(sculpt_multires_active(ob)) { + DerivedMesh *dm = mesh_get_derived_final(NULL, ob, CD_MASK_BAREMESH); /* XXX scene=? */ + ss->multires = 1; + ss->totvert = dm->getNumVerts(dm); + ss->totface = dm->getNumFaces(dm); + ss->mvert = dm->getVertDataArray(dm, CD_MVERT); + ss->mface = dm->getFaceDataArray(dm, CD_MFACE); + ss->face_normals = dm->getFaceDataArray(dm, CD_NORMAL); + } + else { + Mesh *me = get_mesh(ob); + ss->multires = 0; + ss->totvert = me->totvert; + ss->totface = me->totface; + ss->mvert = me->mvert; + ss->mface = me->mface; + ss->face_normals = NULL; + } +} + +/* XXX: lots of drawing code (partial redraw), has to go elsewhere */ +#if 0 +void sculptmode_draw_wires(SculptSession *ss, int only_damaged) +{ + Mesh *me = get_mesh(OBACT); + int i; + + bglPolygonOffset(1.0); + glDepthMask(0); + BIF_ThemeColor((OBACT==OBACT)?TH_ACTIVE:TH_SELECT); + + for(i=0; itotedge; i++) { + MEdge *med= &me->medge[i]; + + if((!only_damaged || (ss->projverts[med->v1].inside || ss->projverts[med->v2].inside)) && + (med->flag & ME_EDGEDRAW)) { + glDrawElements(GL_LINES, 2, GL_UNSIGNED_INT, &med->v1); + } + } + + glDepthMask(1); + bglPolygonOffset(0.0); +} + +void sculptmode_draw_mesh(int only_damaged) +{ + int i, j, dt, drawCurrentMat = 1, matnr= -1; + SculptSession *ss = sculpt_session(); + + sculpt_update_mesh_elements(ss, OBACT); + + persp(PERSP_VIEW); + mymultmatrix(OBACT->obmat); + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); + /* XXX: GPU_set_object_materials(G.scene, OBACT, 0, NULL); */ + glEnable(GL_CULL_FACE); + + glShadeModel(GL_SMOOTH); + + glVertexPointer(3, GL_FLOAT, sizeof(MVert), &ss->mvert[0].co); + glNormalPointer(GL_SHORT, sizeof(MVert), &ss->mvert[0].no); + + dt= MIN2(G.vd->drawtype, OBACT->dt); + if(dt==OB_WIRE) + glColorMask(0,0,0,0); + + for(i=0; itotface; ++i) { + MFace *f= &ss->mface[i]; + char inside= 0; + int new_matnr= f->mat_nr + 1; + + if(new_matnr != matnr) + drawCurrentMat= GPU_enable_material(matnr = new_matnr, NULL); + + /* If only_damaged!=0, only draw faces that are partially + inside the area(s) modified by the brush */ + if(only_damaged) { + for(j=0; j<(f->v4?4:3); ++j) { + if(ss->projverts[*((&f->v1)+j)].inside) { + inside= 1; + break; + } + } + } + else + inside= 1; + + if(inside && drawCurrentMat) + glDrawElements(f->v4?GL_QUADS:GL_TRIANGLES, f->v4?4:3, GL_UNSIGNED_INT, &f->v1); + } + + glDisable(GL_CULL_FACE); + glDisable(GL_LIGHTING); + glColorMask(1,1,1,1); + + if(dt==OB_WIRE || (OBACT->dtx & OB_DRAWWIRE)) + sculptmode_draw_wires(ss, only_damaged); + + glDisable(GL_DEPTH_TEST); +} +#endif + +void sculptmode_correct_state(void) +{ + if(!sculpt_session()) + sculpt_init_session(); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + if(!sculpt_session()->vertex_users) calc_vertex_users(); +} + +void sculpt(void) +{ + SculptData *sd= sculpt_data(); + SculptSession *ss= sculpt_session(); + Object *ob= NULL; /*XXX */ + Mesh *me; + MultiresModifierData *mmd = NULL; + /* lastSigMouse is for the rake, to store the last place the mouse movement was significant */ + short mouse[2], mvalo[2], lastSigMouse[2],firsttime=1, mousebut; + short modifier_calculations= 0; + BrushAction *a = MEM_callocN(sizeof(BrushAction), "brush action"); + short spacing= 32000; + int scissor_box[4]; + float offsetRot; + int smooth_stroke = 0, i; + int anchored; + + /* XXX: checking that sculpting is allowed + if(!(G.f & G_SCULPTMODE) || G.obedit || !ob || ob->id.lib || !get_mesh(ob) || (get_mesh(ob)->totface == 0)) + return; + if(!(ob->lay & G.vd->lay)) + error("Active object is not in this layer"); + if(ob_get_keyblock(ob)) { + if(!(ob->shapeflag & OB_SHAPE_LOCK)) { + error("Cannot sculpt on unlocked shape key"); + return; + } + }*/ + + if(!ss) { + sculpt_init_session(); + ss= sd->session; + } + + anchored = sculptmode_brush()->flag & SCULPT_BRUSH_ANCHORED; + smooth_stroke = (sd->flags & SCULPT_INPUT_SMOOTH) && (sd->brush_type != GRAB_BRUSH) && !anchored; + + if(smooth_stroke) + sculpt_stroke_new(256); + + ss->damaged_rects.first = ss->damaged_rects.last = NULL; + ss->damaged_verts.first = ss->damaged_verts.last = NULL; + ss->vertexcosnos = NULL; + + mmd = sculpt_multires_active(ob); + sculpt_update_mesh_elements(ss, ob); + + /* Check that vertex users are up-to-date */ + if(ob != active_ob || !ss->vertex_users || ss->vertex_users_size != ss->totvert) { + sculpt_vertexusers_free(ss); + calc_vertex_users(); + if(ss->projverts) + MEM_freeN(ss->projverts); + ss->projverts = NULL; + active_ob= ob; + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + /*XXX: + persp(PERSP_VIEW); + getmouseco_areawin(mvalo);*/ + + /* Init texture + FIXME: Shouldn't be doing this every time! */ + if(sd->texrept!=SCULPTREPT_3D) + sculptmode_update_tex(); + + getmouseco_areawin(mouse); + mvalo[0]= mouse[0]; + mvalo[1]= mouse[1]; + lastSigMouse[0]=mouse[0]; + lastSigMouse[1]=mouse[1]; + mousebut = 0; /* XXX: L_MOUSE; */ + + /* If modifier_calculations is true, then extra time must be spent + updating the mesh. This takes a *lot* longer, so it's worth + skipping if the modifier stack is empty. */ + modifier_calculations= sculpt_modifiers_active(ob); + + init_sculptmatrices(); + + if(modifier_calculations) + ss->vertexcosnos= mesh_get_mapped_verts_nors(NULL, ob); /* XXX: scene = ? */ + sculptmode_update_all_projverts(ss->vertexcosnos); + + /* Set scaling adjustment */ + a->scale[0]= 1.0f / ob->size[0]; + a->scale[1]= 1.0f / ob->size[1]; + a->scale[2]= 1.0f / ob->size[2]; + + /* Capture original copy */ + if(sd->flags & SCULPT_DRAW_FAST) + glAccum(GL_LOAD, 1); + + /* Get original scissor box */ + glGetIntegerv(GL_SCISSOR_BOX, scissor_box); + + /* For raking, get the original angle*/ + offsetRot=sculpt_tex_angle(); + + me = get_mesh(ob); + + while (get_mbut() & mousebut) { + getmouseco_areawin(mouse); + /* If rake, and the mouse has moved over 10 pixels (euclidean) (prevents jitter) then get the new angle */ + if (sd->rake && (pow(lastSigMouse[0]-mouse[0],2)+pow(lastSigMouse[1]-mouse[1],2))>100){ + /*Nasty looking, but just orig + new angle really*/ + set_tex_angle(offsetRot+180.+to_deg(atan2((float)(mouse[1]-lastSigMouse[1]),(float)(mouse[0]-lastSigMouse[0])))); + lastSigMouse[0]=mouse[0]; + lastSigMouse[1]=mouse[1]; + } + + if(firsttime || mouse[0]!=mvalo[0] || mouse[1]!=mvalo[1] || + sculptmode_brush()->flag & SCULPT_BRUSH_AIRBRUSH) { + a->firsttime = firsttime; + firsttime= 0; + + if(smooth_stroke) + sculpt_stroke_add_point(mouse[0], mouse[1]); + + spacing+= sqrt(pow(mvalo[0]-mouse[0],2)+pow(mvalo[1]-mouse[1],2)); + + if(modifier_calculations && !ss->vertexcosnos) + ss->vertexcosnos= mesh_get_mapped_verts_nors(NULL, ob); /*XXX scene = ? */ + + if(sd->brush_type != GRAB_BRUSH) { + if(anchored) { + /* Restore the mesh before continuing with anchored stroke */ + if(a->mesh_store) { + for(i = 0; i < ss->totvert; ++i) { + VecCopyf(ss->mvert[i].co, &a->mesh_store[i].x); + ss->mvert[i].no[0] = a->orig_norms[i][0]; + ss->mvert[i].no[1] = a->orig_norms[i][1]; + ss->mvert[i].no[2] = a->orig_norms[i][2]; + } + } + + do_symmetrical_brush_actions(a, mouse, NULL); + } + else { + if(smooth_stroke) { + sculpt_stroke_apply(a); + } + else if(sd->spacing==0 || spacing>sd->spacing) { + do_symmetrical_brush_actions(a, mouse, NULL); + spacing= 0; + } + } + } + else { + do_symmetrical_brush_actions(a, mouse, mvalo); + unproject(sd->pivot, mouse[0], mouse[1], a->depth); + } + + if((!ss->multires && modifier_calculations) || ob_get_keyblock(ob)) + ;/* XXX: DAG_object_flush_update(G.scene, ob, OB_RECALC_DATA); */ + + if(modifier_calculations || sd->brush_type == GRAB_BRUSH || !(sd->flags & SCULPT_DRAW_FAST)) { + calc_damaged_verts(&ss->damaged_verts, a); + /*XXX: scrarea_do_windraw(curarea); */ + screen_swapbuffers(); + } else { /* Optimized drawing */ + calc_damaged_verts(&ss->damaged_verts, a); + + /* Draw the stored image to the screen */ + glAccum(GL_RETURN, 1); + + sculpt_clear_damaged_areas(ss); + + /* Draw all the polygons that are inside the modified area(s) */ + glScissor(scissor_box[0], scissor_box[1], scissor_box[2], scissor_box[3]); + sculptmode_draw_mesh(1); + glAccum(GL_LOAD, 1); + + projverts_clear_inside(ss); + + /* XXX: persp(PERSP_WIN); */ + glDisable(GL_DEPTH_TEST); + + /* Draw cursor */ + if(sculpt_data()->flags & SCULPT_DRAW_BRUSH) + fdrawXORcirc((float)mouse[0],(float)mouse[1],sculptmode_brush()->size); + if(smooth_stroke) + sculpt_stroke_draw(); + + myswapbuffers(); + } + + BLI_freelistN(&ss->damaged_rects); + ss->damaged_rects.first = ss->damaged_rects.last = NULL; + + mvalo[0]= mouse[0]; + mvalo[1]= mouse[1]; + + if(ss->vertexcosnos) { + MEM_freeN(ss->vertexcosnos); + ss->vertexcosnos= NULL; + } + + } + else BIF_wait_for_statechange(); + } + + /* Set the rotation of the brush back to what it was before any rake */ + set_tex_angle(offsetRot); + + if(smooth_stroke) { + sculpt_stroke_apply_all(a); + calc_damaged_verts(&ss->damaged_verts, a); + BLI_freelistN(&ss->damaged_rects); + } + + if(a->layer_disps) MEM_freeN(a->layer_disps); + if(a->mesh_store) MEM_freeN(a->mesh_store); + if(a->orig_norms) MEM_freeN(a->orig_norms); + for(i=0; i<8; ++i) + BLI_freelistN(&a->grab_active_verts[i]); + MEM_freeN(a); + sculpt_stroke_free(); + + if(mmd) { + if(mmd->undo_verts && mmd->undo_verts != ss->mvert) + MEM_freeN(mmd->undo_verts); + + mmd->undo_verts = ss->mvert; + mmd->undo_verts_tot = ss->totvert; + } + + /* XXX: sculpt_undo_push(G.scene->sculptdata.brush_type); */ + + /* XXX: if(G.vd->depths) G.vd->depths->damaged= 1; + allqueue(REDRAWVIEW3D, 0); */ +} + +static void sculpt_undo_push(const short brush_type) +{ + switch(brush_type) { + case DRAW_BRUSH: + BIF_undo_push("Draw Brush"); break; + case SMOOTH_BRUSH: + BIF_undo_push("Smooth Brush"); break; + case PINCH_BRUSH: + BIF_undo_push("Pinch Brush"); break; + case INFLATE_BRUSH: + BIF_undo_push("Inflate Brush"); break; + case GRAB_BRUSH: + BIF_undo_push("Grab Brush"); break; + case LAYER_BRUSH: + BIF_undo_push("Layer Brush"); break; + case FLATTEN_BRUSH: + BIF_undo_push("Flatten Brush"); break; + default: + BIF_undo_push("Sculpting"); break; + } +} + +void set_sculptmode(void) +{ + if(G.f & G_SCULPTMODE) { + Object *ob = NULL; /*XXX: OBACT; */ + Mesh *me= get_mesh(ob); + + multires_force_update(ob); + + G.f &= ~G_SCULPTMODE; + + /* XXX: sculptsession_free(G.scene); */ + if(me && me->pv) + mesh_pmv_off(ob, me); + } + else { + G.f |= G_SCULPTMODE; + + /* Called here to sanity-check the brush */ + sculptmode_brush(); + + sculpt_init_session(); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + } + + active_ob= NULL; +} + +/* Partial Mesh Visibility */ + +/* mode: 0=hide outside selection, 1=hide inside selection */ +static void sculptmode_do_pmv(Object *ob, rcti *hb_2d, int mode) +{ + Mesh *me= get_mesh(ob); + float hidebox[6][3]; + vec3f plane_normals[4]; + float plane_ds[4]; + unsigned i, j; + unsigned ndx_show, ndx_hide; + MVert *nve; + unsigned face_cnt_show= 0, face_ndx_show= 0; + unsigned edge_cnt_show= 0, edge_ndx_show= 0; + unsigned *old_map= NULL; + const unsigned SHOW= 0, HIDE=1; + + /* Convert hide box from 2D to 3D */ + unproject(hidebox[0], hb_2d->xmin, hb_2d->ymax, 1); + unproject(hidebox[1], hb_2d->xmax, hb_2d->ymax, 1); + unproject(hidebox[2], hb_2d->xmax, hb_2d->ymin, 1); + unproject(hidebox[3], hb_2d->xmin, hb_2d->ymin, 1); + unproject(hidebox[4], hb_2d->xmin, hb_2d->ymax, 0); + unproject(hidebox[5], hb_2d->xmax, hb_2d->ymin, 0); + + /* Calculate normals for each side of hide box */ + CalcNormFloat(hidebox[0], hidebox[1], hidebox[4], &plane_normals[0].x); + CalcNormFloat(hidebox[1], hidebox[2], hidebox[5], &plane_normals[1].x); + CalcNormFloat(hidebox[2], hidebox[3], hidebox[5], &plane_normals[2].x); + CalcNormFloat(hidebox[3], hidebox[0], hidebox[4], &plane_normals[3].x); + + /* Calculate D for each side of hide box */ + for(i= 0; i<4; ++i) + plane_ds[i]= hidebox[i][0]*plane_normals[i].x + hidebox[i][1]*plane_normals[i].y + + hidebox[i][2]*plane_normals[i].z; + + /* Add partial visibility to mesh */ + if(!me->pv) { + me->pv= MEM_callocN(sizeof(PartialVisibility),"PartialVisibility"); + } else { + old_map= MEM_callocN(sizeof(unsigned)*me->pv->totvert,"PMV oldmap"); + for(i=0; ipv->totvert; ++i) { + old_map[i]= me->pv->vert_map[i]totvert?0:1; + } + mesh_pmv_revert(ob, me); + } + + /* Kill sculpt data */ + active_ob= NULL; + + /* Initalize map with which verts are to be hidden */ + me->pv->vert_map= MEM_mallocN(sizeof(unsigned)*me->totvert, "PMV vertmap"); + me->pv->totvert= me->totvert; + me->totvert= 0; + for(i=0; ipv->totvert; ++i) { + me->pv->vert_map[i]= mode ? HIDE:SHOW; + for(j=0; j<4; ++j) { + if(me->mvert[i].co[0] * plane_normals[j].x + + me->mvert[i].co[1] * plane_normals[j].y + + me->mvert[i].co[2] * plane_normals[j].z < plane_ds[j] ) { + me->pv->vert_map[i]= mode ? SHOW:HIDE; /* Vert is outside the hide box */ + break; + } + } + if(old_map && old_map[i]) me->pv->vert_map[i]= 1; + if(!me->pv->vert_map[i]) ++me->totvert; + + } + if(old_map) MEM_freeN(old_map); + + /* Find out how many faces to show */ + for(i=0; itotface; ++i) { + if(!me->pv->vert_map[me->mface[i].v1] && + !me->pv->vert_map[me->mface[i].v2] && + !me->pv->vert_map[me->mface[i].v3]) { + if(me->mface[i].v4) { + if(!me->pv->vert_map[me->mface[i].v4]) + ++face_cnt_show; + } + else ++face_cnt_show; + } + } + /* Find out how many edges to show */ + for(i=0; itotedge; ++i) { + if(!me->pv->vert_map[me->medge[i].v1] && + !me->pv->vert_map[me->medge[i].v2]) + ++edge_cnt_show; + } + + /* Create new vert array and reset each vert's map with map[old]=new index */ + nve= MEM_mallocN(sizeof(MVert)*me->pv->totvert, "PMV verts"); + ndx_show= 0; ndx_hide= me->totvert; + for(i=0; ipv->totvert; ++i) { + if(me->pv->vert_map[i]) { + me->pv->vert_map[i]= ndx_hide; + nve[me->pv->vert_map[i]]= me->mvert[i]; + ++ndx_hide; + } else { + me->pv->vert_map[i]= ndx_show; + nve[me->pv->vert_map[i]]= me->mvert[i]; + ++ndx_show; + } + } + CustomData_free_layer_active(&me->vdata, CD_MVERT, me->pv->totvert); + me->mvert= CustomData_add_layer(&me->vdata, CD_MVERT, CD_ASSIGN, nve, me->totvert); + + /* Create new face array */ + me->pv->old_faces= me->mface; + me->pv->totface= me->totface; + me->mface= MEM_mallocN(sizeof(MFace)*face_cnt_show, "PMV faces"); + for(i=0; itotface; ++i) { + MFace *pr_f= &me->pv->old_faces[i]; + char show= 0; + + if(me->pv->vert_map[pr_f->v1] < me->totvert && + me->pv->vert_map[pr_f->v2] < me->totvert && + me->pv->vert_map[pr_f->v3] < me->totvert) { + if(pr_f->v4) { + if(me->pv->vert_map[pr_f->v4] < me->totvert) + show= 1; + } + else show= 1; + } + + if(show) { + MFace *cr_f= &me->mface[face_ndx_show]; + *cr_f= *pr_f; + cr_f->v1= me->pv->vert_map[pr_f->v1]; + cr_f->v2= me->pv->vert_map[pr_f->v2]; + cr_f->v3= me->pv->vert_map[pr_f->v3]; + cr_f->v4= pr_f->v4 ? me->pv->vert_map[pr_f->v4] : 0; + test_index_face(cr_f,NULL,0,pr_f->v4?4:3); + ++face_ndx_show; + } + } + me->totface= face_cnt_show; + CustomData_set_layer(&me->fdata, CD_MFACE, me->mface); + + /* Create new edge array */ + me->pv->old_edges= me->medge; + me->pv->totedge= me->totedge; + me->medge= MEM_mallocN(sizeof(MEdge)*edge_cnt_show, "PMV edges"); + me->pv->edge_map= MEM_mallocN(sizeof(int)*me->pv->totedge,"PMV edgemap"); + for(i=0; itotedge; ++i) { + if(me->pv->vert_map[me->pv->old_edges[i].v1] < me->totvert && + me->pv->vert_map[me->pv->old_edges[i].v2] < me->totvert) { + MEdge *cr_e= &me->medge[edge_ndx_show]; + me->pv->edge_map[i]= edge_ndx_show; + *cr_e= me->pv->old_edges[i]; + cr_e->v1= me->pv->vert_map[me->pv->old_edges[i].v1]; + cr_e->v2= me->pv->vert_map[me->pv->old_edges[i].v2]; + ++edge_ndx_show; + } + else me->pv->edge_map[i]= -1; + } + me->totedge= edge_cnt_show; + CustomData_set_layer(&me->edata, CD_MEDGE, me->medge); + + /* XXX: DAG_object_flush_update(G.scene, OBACT, OB_RECALC_DATA); */ +} + +static rcti sculptmode_pmv_box() +{ + /*XXX: short down[2], mouse[2]; + rcti ret; + + getmouseco_areawin(down); + + while((get_mbut()&L_MOUSE) || (get_mbut()&R_MOUSE)) { + getmouseco_areawin(mouse); + + scrarea_do_windraw(curarea); + + persp(PERSP_WIN); + glLineWidth(2); + setlinestyle(2); + sdrawXORline(down[0],down[1],mouse[0],down[1]); + sdrawXORline(mouse[0],down[1],mouse[0],mouse[1]); + sdrawXORline(mouse[0],mouse[1],down[0],mouse[1]); + sdrawXORline(down[0],mouse[1],down[0],down[1]); + setlinestyle(0); + glLineWidth(1); + persp(PERSP_VIEW); + + screen_swapbuffers(); + backdrawview3d(0); + } + + ret.xmin= down[0]mouse[0]?down[0]:mouse[0]; + ret.ymax= down[1]>mouse[1]?down[1]:mouse[1]; + return ret;*/ +} + +void sculptmode_pmv(int mode) +{ + Object *ob= NULL; /*XXX: OBACT; */ + rcti hb_2d; + + if(ob_get_key(ob)) { + error("Cannot hide mesh with shape keys enabled"); + return; + } + + hb_2d= sculptmode_pmv_box(); /* Get 2D hide box */ + + sculptmode_correct_state(); + + waitcursor(1); + + if(hb_2d.xmax-hb_2d.xmin > 3 && hb_2d.ymax-hb_2d.ymin > 3) { + init_sculptmatrices(); + + sculptmode_do_pmv(ob,&hb_2d,mode); + } + else mesh_pmv_off(ob, get_mesh(ob)); + + /*XXX: scrarea_do_windraw(curarea); */ + + BIF_undo_push("Partial mesh hide"); + + waitcursor(0); +} diff --git a/source/blender/editors/sculpt/sculpt_intern.h b/source/blender/editors/sculpt/sculpt_intern.h new file mode 100644 index 00000000000..04112da55d3 --- /dev/null +++ b/source/blender/editors/sculpt/sculpt_intern.h @@ -0,0 +1,87 @@ +/* + * $Id: BDR_sculptmode.h 13396 2008-01-25 04:17:38Z nicholasbishop $ + * + * ***** 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 ***** + */ + +#ifndef BDR_SCULPTMODE_H +#define BDR_SCULPTMODE_H + +#include "DNA_listBase.h" +#include "DNA_vec_types.h" +#include "BKE_sculpt.h" + +struct uiBlock; +struct BrushAction; +struct BrushData; +struct IndexNode; +struct KeyBlock; +struct Mesh; +struct Object; +struct PartialVisibility; +struct Scene; +struct ScrArea; +struct SculptData; +struct SculptStroke; + +struct SculptSession *sculpt_session(void); +struct SculptData *sculpt_data(void); + +/* Memory */ +void sculptmode_correct_state(void); + +/* Interface */ +void sculptmode_draw_interface_tools(struct uiBlock *block,unsigned short cx, unsigned short cy); +void sculptmode_draw_interface_brush(struct uiBlock *block,unsigned short cx, unsigned short cy); +void sculptmode_draw_interface_textures(struct uiBlock *block,unsigned short cx, unsigned short cy); +void sculptmode_rem_tex(void*,void*); +void sculptmode_selectbrush_menu(void); +void sculptmode_draw_mesh(int); +void sculpt_paint_brush(char clear); +void sculpt_stroke_draw(); +void sculpt_radialcontrol_start(int mode); + +struct BrushData *sculptmode_brush(void); +void do_symmetrical_brush_actions(struct BrushAction *a, short *, short *); + +void sculptmode_update_tex(void); +char sculpt_modifiers_active(struct Object *ob); +void sculpt(void); +void set_sculptmode(void); + +/* Stroke */ +void sculpt_stroke_new(const int max); +void sculpt_stroke_free(); +void sculpt_stroke_add_point(const short x, const short y); +void sculpt_stroke_apply(struct BrushAction *); +void sculpt_stroke_apply_all(struct BrushAction *); +void sculpt_stroke_draw(); + + +/* Partial Mesh Visibility */ +void sculptmode_pmv(int mode); + +#endif diff --git a/source/blender/editors/sculpt/stroke.c b/source/blender/editors/sculpt/stroke.c new file mode 100644 index 00000000000..482b2fcefe0 --- /dev/null +++ b/source/blender/editors/sculpt/stroke.c @@ -0,0 +1,284 @@ +/* + * $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) 2007 by Nicholas Bishop + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + * + * Storage and manipulation of sculptmode brush strokes. + * + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_listBase.h" +#include "DNA_scene_types.h" + +#include "BKE_sculpt.h" +#include "BLI_blenlib.h" +#include "BIF_gl.h" + +#include "sculpt_intern.h" + +#include + +/* Temporary storage of input stroke control points */ +typedef struct StrokePoint { + struct StrokePoint *next, *prev; + short x, y; +} StrokePoint; +typedef struct SculptStroke { + short (*loc)[2]; + int max; + int index; + float length; + ListBase final; + StrokePoint *final_mem; + float offset; +} SculptStroke; + +void sculpt_stroke_new(const int max) +{ + SculptSession *ss = sculpt_session(); + + ss->stroke = MEM_callocN(sizeof(SculptStroke), "SculptStroke"); + ss->stroke->loc = MEM_callocN(sizeof(short) * 4 * max, "SculptStroke.loc"); + ss->stroke->max = max; + ss->stroke->index = -1; +} + +void sculpt_stroke_free() +{ + SculptSession *ss = sculpt_session(); + if(ss && ss->stroke) { + if(ss->stroke->loc) MEM_freeN(ss->stroke->loc); + if(ss->stroke->final_mem) MEM_freeN(ss->stroke->final_mem); + + MEM_freeN(ss->stroke); + ss->stroke = NULL; + } +} + +void sculpt_stroke_add_point(const short x, const short y) +{ + SculptStroke *stroke = sculpt_session()->stroke; + const int next = stroke->index + 1; + + if(stroke->index == -1) { + stroke->loc[0][0] = x; + stroke->loc[0][1] = y; + stroke->index = 0; + } + else if(next < stroke->max) { + const int dx = x - stroke->loc[stroke->index][0]; + const int dy = y - stroke->loc[stroke->index][1]; + stroke->loc[next][0] = x; + stroke->loc[next][1] = y; + stroke->length += sqrt(dx*dx + dy*dy); + stroke->index = next; + } +} + +void sculpt_stroke_smooth(SculptStroke *stroke) +{ + /* Apply smoothing (exclude the first and last points)*/ + StrokePoint *p = stroke->final.first; + if(p && p->next && p->next->next) { + for(p = p->next->next; p && p->next && p->next->next; p = p->next) { + p->x = p->prev->prev->x*0.1 + p->prev->x*0.2 + p->x*0.4 + p->next->x*0.2 + p->next->next->x*0.1; + p->y = p->prev->prev->y*0.1 + p->prev->y*0.2 + p->y*0.4 + p->next->y*0.2 + p->next->next->y*0.1; + } + } +} + +static void sculpt_stroke_create_final() +{ + SculptStroke *stroke = sculpt_session()->stroke; + + if(stroke) { + StrokePoint *p, *pnext; + int i; + + /* Copy loc into final */ + if(stroke->final_mem) + MEM_freeN(stroke->final_mem); + stroke->final_mem = MEM_callocN(sizeof(StrokePoint) * (stroke->index + 1) * 2, "SculptStroke.final"); + stroke->final.first = stroke->final.last = NULL; + for(i = 0; i <= stroke->index; ++i) { + p = &stroke->final_mem[i]; + p->x = stroke->loc[i][0]; + p->y = stroke->loc[i][1]; + BLI_addtail(&stroke->final, p); + } + + /* Remove shortest edges */ + if(stroke->final.first) { + for(p = ((StrokePoint*)stroke->final.first)->next; p && p->next; p = pnext) { + const int dx = p->x - p->prev->x; + const int dy = p->y - p->prev->y; + const float len = sqrt(dx*dx + dy*dy); + pnext = p->next; + if(len < 10) { + BLI_remlink(&stroke->final, p); + } + } + } + + sculpt_stroke_smooth(stroke); + + /* Subdivide edges */ + for(p = stroke->final.first; p && p->next; p = pnext) { + StrokePoint *np = &stroke->final_mem[i++]; + + pnext = p->next; + np->x = (p->x + p->next->x) / 2; + np->y = (p->y + p->next->y) / 2; + BLI_insertlink(&stroke->final, p, np); + } + + sculpt_stroke_smooth(stroke); + } +} + +float sculpt_stroke_seglen(StrokePoint *p1, StrokePoint *p2) +{ + int dx = p2->x - p1->x; + int dy = p2->y - p1->y; + return sqrt(dx*dx + dy*dy); +} + +float sculpt_stroke_final_length(SculptStroke *stroke) +{ + StrokePoint *p; + float len = 0; + for(p = stroke->final.first; p && p->next; ++p) + len += sculpt_stroke_seglen(p, p->next); + return len; +} + +/* If partial is nonzero, cuts off apply after that length has been processed */ +static StrokePoint *sculpt_stroke_apply_generic(SculptStroke *stroke, struct BrushAction *a, const int partial) +{ + const int sdspace = sculpt_data()->spacing; + const short spacing = sdspace > 0 ? sdspace : 2; + const int dots = sculpt_stroke_final_length(stroke) / spacing; + int i; + StrokePoint *p = stroke->final.first; + float startloc = stroke->offset; + + for(i = 0; i < dots && p && p->next; ++i) { + const float dotloc = spacing * i; + short co[2]; + float len = sculpt_stroke_seglen(p, p->next); + float u, v; + + /* Find edge containing dot */ + while(dotloc > startloc + len && p && p->next && p->next->next) { + p = p->next; + startloc += len; + len = sculpt_stroke_seglen(p, p->next); + } + + if(!p || !p->next || dotloc > startloc + len) + break; + + if(partial && startloc > partial) { + /* Calculate offset for next stroke segment */ + stroke->offset = startloc + len - dotloc; + break; + } + + u = (dotloc - startloc) / len; + v = 1 - u; + + co[0] = p->x*v + p->next->x*u; + co[1] = p->y*v + p->next->y*u; + + do_symmetrical_brush_actions(a, co, NULL); + } + + return p ? p->next : NULL; +} + +void sculpt_stroke_apply(struct BrushAction *a) +{ + SculptStroke *stroke = sculpt_session()->stroke; + /* TODO: make these values user-modifiable? */ + const int partial_len = 100; + const int min_len = 200; + + if(stroke) { + sculpt_stroke_create_final(); + + if(sculpt_stroke_final_length(stroke) > min_len) { + StrokePoint *p = sculpt_stroke_apply_generic(stroke, a, partial_len); + + /* Replace remaining values in stroke->loc with remaining stroke->final values */ + stroke->index = -1; + stroke->length = 0; + for(; p; p = p->next) { + ++stroke->index; + stroke->loc[stroke->index][0] = p->x; + stroke->loc[stroke->index][1] = p->y; + if(p->next) { + stroke->length += sculpt_stroke_seglen(p, p->next); + } + } + } + } +} + +void sculpt_stroke_apply_all(struct BrushAction *a) +{ + SculptStroke *stroke = sculpt_session()->stroke; + + sculpt_stroke_create_final(); + + if(stroke) { + sculpt_stroke_apply_generic(stroke, a, 0); + } +} + +void sculpt_stroke_draw() +{ + SculptStroke *stroke = sculpt_session()->stroke; + + if(stroke) { + StrokePoint *p; + + /* Draws the original stroke */ + /*glColor3f(1, 0, 0); + glBegin(GL_LINE_STRIP); + for(i = 0; i <= stroke->index; ++i) + glVertex2s(stroke->loc[i][0], stroke->loc[i][1]); + glEnd();*/ + + /* Draws the smoothed stroke */ + glColor3f(0, 1, 0); + glBegin(GL_LINE_STRIP); + for(p = stroke->final.first; p; p = p->next) + glVertex2s(p->x, p->y); + glEnd(); + } +}