%%
%%  wpc_sculpt.erl --
%%
%%     A plugin to add a few basic sculpting tools.
%%
%%  Copyright (c) 2010 Richard Jones
%%
%%  See the file "license.terms" for information on usage and redistribution
%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%

-module(wpc_sculpt).
-export([init/0,menu/2,command/2]).

-define(NEED_OPENGL, 1).
-define(NEED_ESDL, 1).
-include_lib("wings.hrl").

-import(lists, [foldl/3,sort/1,reverse/1,member/2]).

-record(sculpt,
    {mode,			% pull, pinch, smooth
     active=false,      	% sculpt active true|false
     id=none,			% id of initial object
     mag,			% magnet true|false
     rad,			% magnet radius
     mag_type,			% magnet type
     str,			% strength
     mir,			% mirror info
     locked,			% masked vertices
     st,			% state
     wst,			% working state
     ost}).			% original state

init() ->
    wings_pref:set_default(sculpt_strength, 0.005),
    wings_pref:set_default(sculpt_mode, pull),
    wings_pref:set_default(sculpt_magnet, {false,1.0}),
    wings_pref:set_default(sculpt_magnet_color,{0.0, 0.0, 1.0, 0.1}),
    wings_pref:set_default(sculpt_magnet_type, dome),
    wings_pref:set_default(sculpt_initial, false),
    true.

menu({tools}, Menu) ->
    reverse(parse(Menu, [], false));
menu(_, Menu) -> Menu.

parse([], NewMenu, true) ->
    NewMenu;
parse([], NewMenu, false) ->
    [sculpt_heading()|NewMenu];
parse([{_,tweak_menu,_}=T|Rest], NewMenu, false) ->
    parse(Rest, [sculpt_heading(),T|NewMenu], true);
parse([Elem|Rest], NewMenu, Found) ->
    parse(Rest, [Elem|NewMenu], Found).

sculpt_heading() ->
    {?__(1,"Sculpt"),sculpt,?__(2,"Enter sculpt mode")}.

command({tools,sculpt}, St) ->
    sculpt_mode_setup(St);
command({sculpt,_}, St) -> St;
command(_,_) -> next.

sculpt_mode_setup(#st{shapes=Shs}=St0) ->
    Active = wings_wm:this(),
    wings_tweak:toggle_draw(false),
    St = wings_undo:init(St0#st{selmode=face,sel=[],sh=false}),
    Mir = mirror_info(Shs, []),
    Mode = wings_pref:get_value(sculpt_mode),
    Str = wings_pref:get_value(sculpt_strength),
    {Mag,Rad} = wings_pref:get_value(sculpt_magnet),
    MagType = wings_pref:get_value(sculpt_magnet_type),
    Lv = shape_attr(gb_trees:to_list(Shs)),
    Sc = #sculpt{mode=Mode,mir=Mir,str=Str,mag=Mag,rad=Rad,mag_type=MagType,
          locked=Lv,st=St,wst=St,ost=St0},
    wings:mode_restriction([face]),
    wings_wm:callback(fun() -> wings_u:menu_restriction(Active, [view]) end),
    {seq,push,update_sculpt_handler(Sc)}.

shape_attr(S) ->
    L = foldl(fun
        ({_,We}, Acc) when ?IS_LIGHT(We) -> Acc;
        ({Id,#we{perm=P,pst=Pst}}, Lv0) when ?IS_SELECTABLE(P) ->
            locked_vs(Pst, Id, Lv0);
        (_, Acc) -> Acc
    end, [], S),
    reverse(L).

locked_vs(Pst, Id, Lv) ->
    case wings_pref:get_value(magnet_mask_on) of
      true ->
        case gb_trees:is_defined(wpc_magnet_mask, Pst) of
          true ->
            [{Id,gb_sets:to_list(wpc_magnet_mask:get_locked_vs(Pst))}|Lv];
          _otherwise ->
            Lv
        end;
      false -> Lv
    end.

update_sculpt_handler(Sc) ->
    {replace,fun(Ev) ->
        handle_sculpt_event_0(Ev, Sc)
    end}.

handle_sculpt_event_0(redraw, #sculpt{st=St}=Sc) ->
    wings:redraw("",St),
    help(Sc),
    update_sculpt_handler(Sc);
handle_sculpt_event_0(Ev, #sculpt{active=true}=Sc) ->
    handle_sculpt_event_1(Ev, Sc);
handle_sculpt_event_0({update_state,St}, Sc) ->
    wings_draw:refresh_dlists(St),
    wings_wm:current_state(St),
    update_sculpt_handler(Sc#sculpt{st=St,wst=St});
handle_sculpt_event_0({current_state,St}, #sculpt{st=St}) ->
    keep;
handle_sculpt_event_0({current_state,#st{shapes=Shs}=St1},
  #sculpt{st=St0}=Sc) ->
    Mir = mirror_info(Shs, []),
    Lv = shape_attr(gb_trees:to_list(Shs)),
    St = wings_undo:save(St0, St1),
    update_sculpt_handler(Sc#sculpt{locked=Lv,mir=Mir,st=St,wst=St});
handle_sculpt_event_0({new_state,#st{shapes=Shs}=St1},
  #sculpt{st=St0}=Sc) ->
    Mir = mirror_info(Shs, []),
    Lv = shape_attr(gb_trees:to_list(Shs)),
    St = wings_undo:save(St0, St1#st{sel=[],selmode=face,sh=false}),
    wings_draw:refresh_dlists(St),
    wings_wm:current_state(St),
    update_sculpt_handler(Sc#sculpt{locked=Lv,mir=Mir,st=St,wst=St});

handle_sculpt_event_0(Ev, #sculpt{st=St}=Sc) ->
    case wings_camera:event(Ev, St) of
      next -> handle_sculpt_event_1(Ev, Sc);
      Other -> Other
    end.

handle_sculpt_event_1({adv_menu_abort,_}, Sc) ->
    update_sculpt_handler(Sc#sculpt{active=true});
handle_sculpt_event_1({note,menu_aborted}, Sc) ->
    update_sculpt_handler(Sc#sculpt{active=true});
handle_sculpt_event_1(#mousemotion{x=X,y=Y}, #sculpt{active=true}=Sc) ->
    do_sculpt(X, Y, Sc);
handle_sculpt_event_1(#mousebutton{state=?SDL_RELEASED},
  #sculpt{st=St,wst=St,active=true}=Sc) ->
    update_sculpt_handler(Sc#sculpt{id=none,active=false});
handle_sculpt_event_1(#mousebutton{state=?SDL_RELEASED},
  #sculpt{st=#st{shapes=Shs},wst=St0,active=true}=Sc) ->
    St = wings_undo:save(St0, St0#st{shapes=Shs}),
    wings_wm:current_state(St),
    update_sculpt_handler(Sc#sculpt{id=none,st=St,wst=St,active=false});
handle_sculpt_event_1(#mousebutton{button=1,x=X,y=Y,state=?SDL_PRESSED},
  #sculpt{st=St}=Sc) ->
    do_sculpt(X, Y, Sc#sculpt{wst=St,active=true});
handle_sculpt_event_1(#mousebutton{button=3,mod=Mod,x=X,y=Y,state=?SDL_PRESSED}, Sc)
  when Mod band ?CTRL_BITS =:= 0 ->
    {GX,GY} = wings_wm:local2global(X, Y),
    adjust_magnet(GX, GY, Sc);
handle_sculpt_event_1(#mousebutton{button=3,mod=Mod,x=X,y=Y,state=?SDL_RELEASED}, _)
  when Mod band ?CTRL_BITS =/= 0 ->
    sculpt_menu(X, Y);
handle_sculpt_event_1(#keyboard{sym=Sym,state=?SDL_PRESSED}=Ev, Sc) ->
    handle_key(Sym, Ev, Sc);
handle_sculpt_event_1({action,Action}, Sc) ->
    command_handling(Action, Sc);
handle_sculpt_event_1(got_focus, #sculpt{st=St}=Sc) ->
    Str = wings_pref:get_value(sculpt_strength),
    wings_wm:dirty(),
    update_sculpt_handler(Sc#sculpt{id=none,st=St#st{selmode=face,sel=[],sh=false},active=false,str=Str});
handle_sculpt_event_1(lost_focus, #sculpt{st=#st{shapes=Shs},wst=St0,active=true}=Sc) ->
    St = wings_undo:save(St0, St0#st{shapes=Shs}),
    wings_wm:current_state(St),
    wings_wm:send(geom,{update_state,St}),
    update_sculpt_handler(Sc#sculpt{id=none,st=St,wst=St,active=false});
handle_sculpt_event_1(_,_) ->
    keep.

%%%
%%% Adjust Magnet
%%%

adjust_magnet(X, Y, Sc) ->
    wings_io:grab(),
    update_magnet_handler(X, Y, Sc).

update_magnet_handler(X, Y, Sc) ->
    wings_wm:dirty(),
    {replace,fun(Ev) ->
        handle_magnet_event(Ev, X, Y, Sc) end}.

handle_magnet_event(redraw, X, Y, #sculpt{st=St}=Sc) ->
    wings_draw:refresh_dlists(St),
    wings:redraw("", St),
    help(Sc),
    draw_magnet(X, Y, Sc),
    update_magnet_handler(X, Y, Sc);
handle_magnet_event(#mousemotion{x=X, y=Y}, X0, Y0, Sc0) ->
    {GX,_} = wings_wm:local2global(X, Y),
    DX = GX-X0, %since last move X
    wings_io:warp(X0,Y0),
    Sc = adjust_magnet_radius(DX, Sc0),
    update_magnet_handler(X0, Y0, Sc);
handle_magnet_event(#mousebutton{button=3,state=?SDL_RELEASED}, X, Y, #sculpt{str=Str}=Sc) ->
    wings_pref:set_value(sculpt_strength, Str),
    wings_io:ungrab(X, Y),
    wings_wm:dirty(),
    update_sculpt_handler(Sc);
handle_magnet_event(#mousebutton{button=4,state=?SDL_RELEASED}, X, Y, Sc0) ->
    Sc = adjust_strength(1, Sc0),
    update_magnet_handler(X, Y, Sc);
handle_magnet_event(#mousebutton{button=5,state=?SDL_RELEASED}, X, Y, Sc0) ->
    Sc = adjust_strength(-1, Sc0),
    update_magnet_handler(X, Y, Sc);
handle_magnet_event(_, X, Y, Sc) -> update_magnet_handler(X, Y, Sc).

%%%
%%% Adjust Radius
%%%

adjust_magnet_radius(0, Sc) ->
    Sc;
adjust_magnet_radius(MouseMovement, #sculpt{rad=Rad0}=Sc)
  when MouseMovement < 0 ->
    case Rad0 - 0.025 of
    Rad when Rad >= 0.01 ->
        Sc#sculpt{rad=Rad};
    _otherwise ->
        Sc#sculpt{rad=0.01}
    end;
adjust_magnet_radius(_, #sculpt{rad=Rad0}=Sc) ->
    Rad = Rad0 + 0.025,
    Sc#sculpt{rad=Rad}.

%%%
%%% Adjust Strength
%%%

adjust_strength(0, Sc) ->
    Sc;
adjust_strength(MouseMovement, #sculpt{str=Str0}=Sc) when MouseMovement < 0 ->
    case Str0 - 0.001 of
    Str when Str > 0.001 ->
        Sc#sculpt{str=Str};
    _otherwise -> Sc#sculpt{str=0.001}
    end;
adjust_strength(_, #sculpt{str=Str0}=Sc) ->
   case Str0 + 0.001 of
    Str when Str < 0.01 ->
        Sc#sculpt{str=Str};
    _otherwise -> Sc#sculpt{str=0.01}
    end.

%%%
%%% Draw Magnet
%%%

draw_magnet(X, Y, #sculpt{rad=Rad,str=Str,st=#st{shapes=Shs}=St}) ->
    {LX,LY} = wings_wm:global2local(X, Y),
    Alpha = Str*100,
    {Xm,Ym,Zm} = case wings_pick:raw_pick(LX, LY, St#st{selmode=face,sel=[],sh=false}) of
      {_,Side,{Id,Face}} ->
          #we{mirror=Mir}=We = gb_trees:get(Id, Shs),
          Point = wings_face:center(Face, We),
          case Side of
              mirror ->
                  Mnorm = wings_face:normal(Mir, We),
                  PointOnPlane = wings_face:center(Mir, We),
                  Dist = dist_along_vector(Point, PointOnPlane, Mnorm),
                  e3d_vec:add_prod(Point, Mnorm, Dist * -2);
              original -> Point
          end;
      none ->
          {0.0,0.0,0.0}
    end,
    gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
    gl:disable(?GL_DEPTH_TEST),
    gl:enable(?GL_BLEND),
    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
    wings_view:load_matrices(false),
    {R,G,B,A} = wings_pref:get_value(sculpt_magnet_color),
    wings_io:set_color({R*Alpha,G*Alpha,B*Alpha,A}),
    gl:translatef(Xm, Ym, Zm),
    Obj = glu:newQuadric(),
    glu:sphere(Obj, Rad, 40, 40),
    glu:deleteQuadric(Obj),
    gl:popAttrib().

dist_along_vector({Xa,Ya,Za},{Xb,Yb,Zb},{Vx,Vy,Vz}) ->
%% Return Distance between PosA and PosB along Normalized Vector
    Vx*(Xa-Xb)+Vy*(Ya-Yb)+Vz*(Za-Zb).

%%%
%%% Sculpt
%%%

do_sculpt(X, Y, Sc) ->
    case sculpt(X, Y, Sc) of
      keep -> keep;
      {St,Id0} ->
        wings_draw:refresh_dlists(St),
        wings_wm:dirty(),
        Id = case wings_pref:get_value(sculpt_initial) of
            true -> Id0;
            false -> none
        end,
        update_sculpt_handler(Sc#sculpt{id=Id,st=St})
    end.

sculpt(X, Y, #sculpt{id=ID,mir=Mir,str=Str0,mag=false,mode=smooth,locked=Locked,
  st=#st{shapes=Shs0}=St}) ->
%% Smooth mode
    case wings_pick:raw_pick(X, Y, St#st{selmode=face,sel=[],sh=false}) of
      {_,_,{Id,Face}} when ID =:= none; Id =:= ID ->
        Lvs = lookup_locked_vs(Id, Locked),
        Str = Str0*10,
        #we{vp=Vtab0}=We = gb_trees:get(Id, Shs0),
        Vtab = wings_face:fold_faces(fun
            (_, V, _, _, Vtab1) ->
                case ordsets:is_element(V, Lvs) of
                    true -> Vtab1;
                    false ->
                        Pos = array:get(V, Vtab0),
                        smooth(V, Pos, 1, Str, Mir, We, Vtab1)
                end
            end, Vtab0, [Face], We),
        Shs = gb_trees:update(Id, We#we{vp=Vtab}, Shs0),
        {St#st{shapes=Shs},Id};
      _ ->
        keep
    end;
sculpt(X, Y, #sculpt{id=ID,mir=Mir,str=Str0,rad=Rad,mag_type=MagType,mode=smooth,
  locked=Locked,st=#st{shapes=Shs0}=St}) ->
%% Smooth mode with magnet
    case wings_pick:raw_pick(X, Y, St#st{selmode=face,sel=[],sh=false}) of
      {_,_,{Id,Face}} when ID =:= none; Id =:= ID ->
        #we{vp=Vtab0}=We = gb_trees:get(Id, Shs0),
        {Positions,_} = vpos(Face, We),
        Cnt = {Cx,Cy,Cz} = e3d_vec:average(Positions),
        Lvs = lookup_locked_vs(Id, Locked),
        Str = Str0*10,
        Vtab = array:sparse_foldl(fun
            (V, Pos={Px,Py,Pz}, Vtab1) ->
                case ordsets:is_element(V, Lvs) of
                    true -> Vtab1;
                    false ->
                        case in_dist_boundaries([Cx,Cy,Cz], [Px,Py,Pz], Rad) of
                            true ->
                                Dist = e3d_vec:dist(Pos, Cnt),
                                case Dist =< Rad of
                                    true ->
                                        Inf = magnetic_influence(MagType, Dist, Rad),
                                        smooth(V, Pos, Inf, Str, Mir, We, Vtab1);
                                    false -> Vtab1
                                end;
                            false -> Vtab1
                        end
                    end
               end, Vtab0, Vtab0),
        Shs = gb_trees:update(Id, We#we{vp=Vtab}, Shs0),
        {St#st{shapes=Shs},Id};
      _ ->
        keep
    end;
sculpt(X, Y, #sculpt{id=ID,mir=Mir,str=Str,mag=false,mode=pinch,locked=Locked,
  st=#st{shapes=Shs0}=St}) ->
%% Pinch mode
    case wings_pick:raw_pick(X, Y, St#st{selmode=face,sel=[],sh=false}) of
      {_,_,{Id,Face}} when ID =:= none; Id =:= ID ->
        Lvs = lookup_locked_vs(Id, Locked),
        #we{vp=Vtab0}=We = gb_trees:get(Id, Shs0),
        {Positions,_} = vpos(Face, We),
        Cnt = e3d_vec:average(Positions),
        Vtab = wings_face:fold_faces(fun
            (_, V, _, _, Vtab1) ->
                case ordsets:is_element(V, Lvs) of
                    true -> Vtab1;
                    false ->
                        Pos = array:get(V, Vtab0),
                        pinch(V, Pos, Cnt, 1, Str, Mir, We, Vtab1)
                end
            end, Vtab0, [Face], We),
        Shs = gb_trees:update(Id, We#we{vp=Vtab}, Shs0),
        {St#st{shapes=Shs},Id};
      _ ->
        keep
    end;
sculpt(X, Y, #sculpt{id=ID,mir=Mir,str=Str,rad=Rad,mag_type=MagType,mode=pinch,
  locked=Locked,st=#st{shapes=Shs0}=St}) ->
%% Pinch mode with magnet
    case wings_pick:raw_pick(X, Y, St#st{selmode=face,sel=[],sh=false}) of
      {_,_,{Id,Face}} when ID =:= none; Id =:= ID ->
        #we{vp=Vtab0}=We = gb_trees:get(Id, Shs0),
        {Positions,_} = vpos(Face, We),
        Cnt = {Cx,Cy,Cz} = e3d_vec:average(Positions),
        Lvs = lookup_locked_vs(Id, Locked),
        Vtab = array:sparse_foldl(fun
            (V, Pos={Px,Py,Pz}, Vtab1) ->
                case ordsets:is_element(V, Lvs) of
                    true -> Vtab1;
                    false ->
                        case in_dist_boundaries([Cx,Cy,Cz], [Px,Py,Pz], Rad) of
                            true ->
                                Dist = e3d_vec:dist(Pos, Cnt),
                                case Dist =< Rad of
                                    true ->
                                        Inf = magnetic_influence(MagType, Dist, Rad),
                                        pinch(V, Pos, Cnt, Inf, Str, Mir, We, Vtab1);
                                    false -> Vtab1
                                end;
                            false -> Vtab1
                        end
                    end
               end, Vtab0, Vtab0),
        Shs = gb_trees:update(Id, We#we{vp=Vtab}, Shs0),
        {St#st{shapes=Shs},Id};
      _ ->
        keep
    end;

sculpt(X, Y, #sculpt{id=ID,locked=Locked,mir=Mir,str=Str,mag=false,
  st=#st{shapes=Shs0}=St}) ->
%% Push and Pull mode
    case wings_pick:raw_pick(X, Y, St#st{selmode=face,sel=[],sh=false}) of
      {_,_,{Id,Face}} when ID =:= none; Id =:= ID ->
        #we{vp=Vtab0}=We = gb_trees:get(Id, Shs0),
        {Positions,Vpos} = vpos(Face, We),
        Lvs = lookup_locked_vs(Id, Locked),
        Normal0 = e3d_vec:normal(Positions),
        Normal = case wings_io:is_modkey_pressed(?CTRL_BITS) of
          false -> Normal0;
          true -> e3d_vec:neg(Normal0)
        end,
        Vtab = foldl(fun
            ({V,Pos}, Vtab1) ->
                 case ordsets:is_element(V, Lvs) of
                     true -> Vtab1;
                     false ->
                         NewPos0 = e3d_vec:add_prod(Pos, Normal, Str),
                         NewPos = handle_mirror(Id, V, NewPos0, Mir),
                         array:set(V, NewPos, Vtab1)
                 end
            end, Vtab0, Vpos),
        Shs = gb_trees:update(Id, We#we{vp=Vtab}, Shs0),
        {St#st{shapes=Shs},Id};
      _ ->
        keep
    end;
sculpt(X, Y, #sculpt{id=ID,locked=Locked,mir=Mir,str=Str,rad=Rad,
  mag_type=MagType,st=#st{shapes=Shs0}=St}) ->
%% Push and Pull mode with magnet
    case wings_pick:raw_pick(X, Y, St#st{selmode=face,sel=[],sh=false}) of
      {_,_,{Id,Face}} when ID =:= none; Id =:= ID ->
        #we{vp=Vtab0}=We = gb_trees:get(Id, Shs0),
        {Positions,_} = vpos(Face, We),
        Cnt = {Cx,Cy,Cz} = e3d_vec:average(Positions),
        Lvs = lookup_locked_vs(Id, Locked),
        {_,Vtab} = array:sparse_foldl(fun
            (V, Pos={Px,Py,Pz}, {FNs0,Vtab1}) ->
                 case ordsets:is_element(V, Lvs) of
                     true -> {FNs0,Vtab1};
                     false ->
                         case in_dist_boundaries([Cx,Cy,Cz], [Px,Py,Pz], Rad) of
                             true ->
                                 Dist = e3d_vec:dist(Pos, Cnt),
                                 case Dist =< Rad of
                                     true ->
                                       Inf = magnetic_influence(MagType, Dist, Rad),
                                       {FNs,Normal0} = vertex_normal(V, We, FNs0),
                                       Normal = case wings_io:is_modkey_pressed(?CTRL_BITS) of
                                         false -> Normal0;
                                         true -> e3d_vec:neg(Normal0)
                                       end,
                                       NewPos0 = e3d_vec:add_prod(Pos, Normal, Str*Inf),
                                       NewPos = handle_mirror(Id, V, NewPos0, Mir),
                                       {FNs,array:set(V, NewPos, Vtab1)};
                                     false -> {FNs0,Vtab1}
                                 end;
                             false -> {FNs0,Vtab1}
                         end
                     end
                end, {gb_trees:empty(),Vtab0}, Vtab0),
        Shs = gb_trees:update(Id, We#we{vp=Vtab}, Shs0),
        {St#st{shapes=Shs},Id};
      _ ->
        keep
    end.

in_dist_boundaries([], [], _) -> true;
in_dist_boundaries([H|T], [P|R], Rad) ->
    Min = H-Rad,
    case P < Min of
      true -> false;
      false ->
        Max = H+Rad,
        case P > Max of
          true -> false;
          false -> in_dist_boundaries(T, R, Rad)
        end
    end.

vertex_normal(V, We, FNormals0) ->
    {Fn,Ns} = wings_vertex:fold(fun(_, Face, _, {FNormals,A}) ->
        case gb_trees:lookup(Face, FNormals) of
            {_,Normal} ->
                {FNormals,[Normal|A]};
            _ ->
                Normal = wings_face:normal(Face, We),
                {gb_trees:insert(Face,Normal,FNormals),[Normal|A]}
        end
    end, {FNormals0,[]}, V, We),
    {Fn,e3d_vec:norm(e3d_vec:add(Ns))}.

vpos(Face, #we{fs=Ftab}=We) ->
    Edge = gb_trees:get(Face, Ftab),
    vpos(Face, Edge, We).

vpos(Face, Edge, #we{es=Etab,vp=Vtab}) ->
    vpos_1(Edge, Etab, Vtab, Face, Edge, [], []).

vpos_1(LastEdge, _, _, _, LastEdge, Acc, Vpos) when Acc =/= [] ->
    {Acc,Vpos};
vpos_1(Edge, Etab, Vtab, Face, LastEdge, Acc, Vpos) ->
    case array:get(Edge, Etab) of
    #edge{vs=V,lf=Face,ltsu=NextEdge} ->
        Pos = array:get(V, Vtab),
        vpos_1(NextEdge, Etab, Vtab, Face, LastEdge, [Pos|Acc], [{V,Pos}|Vpos]);
    #edge{ve=V,rf=Face,rtsu=NextEdge} ->
        Pos = array:get(V, Vtab),
        vpos_1(NextEdge, Etab, Vtab, Face, LastEdge, [Pos|Acc], [{V,Pos}|Vpos])
    end.

lookup_locked_vs(_, []) -> [];
lookup_locked_vs(Id, Locked) ->
    case orddict:find(Id, Locked) of
        {_,Lvs0} -> Lvs0;
        error -> []
    end.

magnetic_influence(bell, Dist, Rad) -> math:sin((Rad-Dist)/Rad*math:pi());
magnetic_influence(dome, Dist, Rad) -> math:sin((Rad-Dist)/Rad*math:pi()/2);
magnetic_influence(straight, Dist, Rad) -> (Rad-Dist)/Rad;
magnetic_influence(spike, Dist, Rad) ->
    D = (Rad-Dist)/Rad,
    D*D;
magnetic_influence(absolute, _, _) -> 1.

smooth(V, Pos, Inf, Str, Mir, #we{id=Id}=We, Vtab) ->
    Positions = wings_vertex:fold(fun(_, _, E, Acc) ->
                 OtherPos = wings_vertex:other_pos(V, E, We),
                 [OtherPos|Acc]
        end, [], V, We),
    Avg = e3d_vec:average(Positions),
    Vec = e3d_vec:sub(Avg, Pos),
    NewPos0 = e3d_vec:add_prod(Pos, Vec, Str*Inf),
    NewPos = handle_mirror(Id, V, NewPos0, Mir),
    array:set(V, NewPos, Vtab).

pinch(V, Pos, Cnt, Inf, Str, Mir, #we{id=Id}, Vtab) ->
    Vec = e3d_vec:sub(Cnt, Pos),
    NewPos0 = e3d_vec:add_prod(Pos, Vec, Str*Inf),
    NewPos = handle_mirror(Id, V, NewPos0, Mir),
    array:set(V, NewPos, Vtab).

%%%
%%% Mirror Handling
%%%

mirror_info(Shs0, Acc0) ->
    case gb_trees:is_empty(Shs0) of
      true -> sort(Acc0);
      false ->
        {Id,#we{mirror=M}=We,Shs} = gb_trees:take_smallest(Shs0),
        Acc = case M of
          none -> Acc0;
          _ ->
            FaceVs = wings_face:vertices_ccw(M, We),
            Flatten = wings_we:mirror_projection(We),
            [{Id,{FaceVs,Flatten}}|Acc0]
        end,
        mirror_info(Shs, Acc)
    end.

handle_mirror(Id, V, Pos, Mir) ->
    case orddict:find(Id, Mir) of
        error -> Pos;
        {ok,{MirVs,Matrix}} ->
            case member(V, MirVs) of
                true -> e3d_mat:mul_point(Matrix, Pos);
                false -> Pos
            end
    end.

%%%
%%% Handle Key Hits
%%%

handle_key(Sym, Ev, Sc) ->
    wings_wm:dirty(),
    case Sym of
      ?SDLK_ESCAPE -> exit_sculpt(Sc);
      _ ->
        case wings_hotkey:event(Ev, sculpt) of
          next -> keep;
          Action -> command_handling(Action, Sc)
        end
    end.

command_handling(Action, #sculpt{st=St0,mag=Mag}=Sc) ->
    case Action of
      {view,highlight_aim} ->
          {{_,Cmd},St1} = wings:highlight_aim_setup(St0),
          St = wings_view:command(Cmd, St1),
          update_sculpt_handler(Sc#sculpt{st=St});
      {view,auto_rotate} -> keep;
      {view,Cmd} ->
          case wings_view:command(Cmd, St0) of
              keep ->
                  keep;
              #st{}=St ->
                  wings_draw:refresh_dlists(St),
                  update_sculpt_handler(Sc#sculpt{st=St})
          end;
      {edit,undo_toggle} ->
          St = wings_u:caption(wings_undo:undo_toggle(St0)),
          wings_wm:current_state(St),
          wings_draw:refresh_dlists(St),
          update_sculpt_handler(Sc#sculpt{st=St});
      {edit,undo} ->
          St = wings_u:caption(wings_undo:undo(St0)),
          wings_wm:current_state(St),
          wings_draw:refresh_dlists(St),
          update_sculpt_handler(Sc#sculpt{st=St});
      {edit,redo} ->
          St = wings_u:caption(wings_undo:redo(St0)),
          wings_wm:current_state(St),
          wings_draw:refresh_dlists(St),
          update_sculpt_handler(Sc#sculpt{st=St});
      {sculpt,Mode} when Mode =:= pull; Mode =:= pinch; Mode =:= smooth ->
          wings_wm:dirty(),
          update_sculpt_handler(Sc#sculpt{mode=Mode});
      {sculpt,MagT} when MagT =:= dome; MagT =:= absolute; MagT =:= straight;
        MagT =:= spike; MagT =:= bell ->
          wings_wm:dirty(),
          update_sculpt_handler(Sc#sculpt{mag=true,mag_type=MagT});
      {sculpt,magnet} ->
          wings_wm:dirty(),
          update_sculpt_handler(Sc#sculpt{mag = not Mag});
      {sculpt,mask_toggle} ->
          MM = wings_pref:get_value(magnet_mask_on),
          wings_pref:set_value(magnet_mask_on, not MM),
          wings_wm:dirty(),
          handle_sculpt_event_0({new_state,St0}, Sc);
      {sculpt,prefs} ->
          prefs(Sc);
      {sculpt,exit_sculpt} ->
          exit_sculpt(Sc);
      _ -> keep
    end.

%%%
%%% Exit Sculpt
%%%

exit_sculpt(#sculpt{mag=Mag,mag_type=MagType,str=Str,rad=Rad,mode=Mode,st=#st{shapes=Shs},ost=St0}) ->
    wings_pref:set_value(sculpt_mode, Mode),
    wings_pref:set_value(sculpt_magnet, {Mag,Rad}),
    wings_pref:set_value(sculpt_strength, Str),
    wings_pref:set_value(sculpt_magnet_type, MagType),
    wings_tweak:toggle_draw(true),
    St = wings_undo:save(St0, St0#st{shapes=Shs}),
    wings_wm:later({new_state,St}),
    pop.

%%%
%%% Info Line
%%%

help(#sculpt{mag=Mag,rad=Rad,mag_type=MagType,str=Str,mode=Mode}) ->
    ModeMsg =
        case Mode of
            pinch -> mode(pinch);
            smooth -> mode(smooth);
            _ ->
              Pull = mode(pull),
              Pull++" "++?__(11,"(Hold [Ctrl]: Push)")
        end,
    Sculpt = ?__(1,"L: Sculpt"),
    Menu = ?__(2,"[Ctrl]+R: Sculpt Menu"),
    MagnetType = io_lib:format(?__(3,"Magnet: ~s"),[magtype(MagType)]),
    Radius = ?__(4,"R+Drag: Adjust Radius"),
    Strength = ?__(5,"R+Scroll: ")  ++ ?__(6,"Adjust Strength"),
    Exit = "[Esc]: " ++ exit_string(),
    StatusBar = io_lib:format(?__(7,"Strength: ~p")++"  "++
                              ?__(8,"Radius: ~s"),
                              [trunc(Str/0.001),
                               if Mag ->
                                   wings_util:nice_float(Rad) ++ "  " ++
                                   MagnetType;
                                  true -> ?__(9,"None (Magnet is off)")
                               end]),
    wings_io:info(wings_msg:join([?__(10,"Sculpt Mode")++": "++ModeMsg, StatusBar])),
    wings_wm:message(wings_msg:join([Sculpt,Menu,Exit]),
                     wings_msg:join([Radius,Strength])).

magtype(bell) -> ?__(1,"Bell");
magtype(dome) -> ?__(2,"Dome");
magtype(straight) -> ?__(3,"Straight");
magtype(spike) -> ?__(4,"Spike");
magtype(absolute) -> ?__(5,"Absolute").

mode(push) -> ?__(1,"Push");
mode(pull) -> ?__(2,"Pull");
mode(pinch) -> ?__(3,"Pinch");
mode(smooth) -> ?__(4,"Smooth").

exit_string() -> ?__(1,"Exit").

%%%
%%% Sculpt Dialog
%%%

prefs(#sculpt{str=Str}) ->
    Strength = trunc(Str/0.001),
    Confine = wings_pref:get_value(sculpt_initial),
    Menu = [{vframe,
      [{hframe,[{slider,{text,Strength,[{key,sculpt_strength},{range,{1,10}}]}}],
         [{title,?__(1,"Strength")}]}]},
       {label_column,
         [{color,?__(2,"Magnet Radius Display Color"),sculpt_magnet_color}]}],
    C = [separator,{?__(3,"Confine sculpt to initial object"),
          Confine,[{key,sculpt_initial}]}],
    PrefQs = [{Lbl, make_query(Ps)} || {Lbl, Ps} <- Menu] ++ C,
    wings_ask:dialog(?__(10,"Sculpt Preferences"), PrefQs,
    fun(Result) -> set_values(Result) end).

make_query([_|_]=List) ->
    [make_query(El) || El <- List];
make_query({color,[_|_]=Str,Key}) ->
    Def = wings_pref:get_value(Key),
    {Str,{color,Def,[{key,Key}]}};
make_query(Other) -> Other.

set_values([{sculpt_strength=Key, Value}|Result]) ->
    wings_pref:set_value(Key, Value*0.001),
    set_values(Result);
set_values([{Key, Value}|Result]) ->
    wings_pref:set_value(Key, Value),
    set_values(Result);
set_values([]) -> ok.

%%%
%%% Sculpt Menu
%%%

sculpt_menu(X0, Y0) ->
    {X,Y} =  wings_wm:local2global(X0, Y0),
    Menu = sculpt_menu(),
    wings_menu:popup_menu(X, Y, sculpt, Menu).

sculpt_menu() ->
    [{mode(push)++"/"++mode(pull),pull},
     {mode(pinch),pinch},
     {mode(smooth),smooth},
     separator,
     {magtype(dome),dome},
     {magtype(absolute),absolute},
     {magtype(straight),straight},
     {magtype(spike),spike},
     {magtype(bell),bell},
     separator,
     {?__(1,"Magnet On/Off"),magnet},
     {?__(2,"Magnet Mask On/Off"),mask_toggle},
     separator,
     {?__(3,"Preferences"),prefs},
     separator,
     {exit_string(),exit_sculpt}].
