1%%
2%%  wpc_thread --
3%%
4%%     Helicoidal and Non-Helicoidal Thread Plugin
5%%
6%%  Copyright (c) 2020 Micheus
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11%%
12
13-module(wpc_thread).
14-export([init/0,menu/2,command/2]).
15-include_lib("src/wings.hrl").
16-import(math, [cos/1,sin/1,pi/0]).
17
18init() -> true.
19
20menu({shape}, Menu) ->
21    init_menu(Menu);
22menu(_, Menu) -> Menu.
23
24init_menu(Menu) ->
25    case parse_menu(Menu,[]) of
26        [] -> Menu ++ menu();
27        Menu0 -> Menu0
28    end.
29
30menu() ->
31    [{thread(),thread,?__(2,"Create a thread"),[option]}].
32
33parse_menu([],_) -> [];
34parse_menu([{_,spring,_}=Spiral|Rest],Acc) ->
35    lists:reverse([Spiral|Acc]) ++ menu() ++ Rest;
36parse_menu([Elem|Rest],Acc) ->
37    parse_menu(Rest, [Elem|Acc]).
38
39
40thread() ->
41    ?__(1,"Thread").
42
43command({shape,{thread, Ask}}, St) -> make_thread(Ask, St);
44command(_, _) -> next.
45
46%%% The rest are local functions.
47
48%%%
49%%% Thread
50%%%
51
52thread_dialog() ->
53    Hook = fun(Var, Val, Sto) ->
54            case Var of
55                thread_type ->
56                    wings_dialog:enable(direction, Val==helicoid, Sto);
57                _ -> ok
58            end
59           end,
60
61    [{label_column, [
62        {?__(1,"Sections"), {text,8,[{key,sections},{range,{3,infinity}}]}},
63        {" ", separator},
64        {?__(2,"Top Radius"), {text,0.5,[{key,top_radius},{range,{0.0,infinity}}]}},
65        {?__(3,"Bottom Radius"), {text,0.5,[{key,bottom_radius},{range,{0.0,infinity}}]}},
66        {" ", separator},
67        {?__(4,"Pitch"), {text,0.3,[{key,pitch},{range,{0.0001,infinity}}]}},
68        {?__(5,"Crest Height"), {text,0.35,[{key,crest_h},{range,{0.0,infinity}}]}},
69        {?__(6,"Crest Amount"), {text,5,[{key,occurrences},{range,{1,infinity}}]}}]
70     },
71     {hradio, [
72         {?__(10,"Helicoidal"),helicoid},
73         {?__(11,"Non-helicoidal"),non_helicoid}],
74      non_helicoid, [{key,thread_type},{title,?__(12,"Thread Type")},{hook,Hook}]},
75     {hradio, [
76         {?__(7,"Left hand"),left},
77         {?__(8,"Right hand"),right}],
78      right, [{key,direction},{title,?__(9,"Direction")}]},
79     wings_shapes:transform_obj_dlg()].
80
81make_thread(Arg, St) when is_atom(Arg) ->
82    Qs = thread_dialog(),
83    Label = ?__(1,"Thread Options"),
84    wings_dialog:dialog_preview({shape,thread}, Arg, Label, Qs, St);
85make_thread(Arg0, _St) ->
86    Arg = maps:from_list(Arg0),
87    Sections = maps:get(sections, Arg),
88    Pitch = maps:get(pitch, Arg),
89    TopRadius = maps:get(top_radius, Arg),
90    BotRadius = maps:get(bottom_radius, Arg),
91    CrestH = maps:get(crest_h, Arg),
92    Occurrences = maps:get(occurrences, Arg),
93    Dir = maps:get(direction, Arg),
94    Type = maps:get(thread_type, Arg),
95    Modify = [{maps:get(rot_x, Arg), maps:get(rot_y, Arg), maps:get(rot_z, Arg)},
96              {maps:get(mov_x, Arg), maps:get(mov_y, Arg), maps:get(mov_z, Arg)},
97              maps:get(ground, Arg)],
98
99    Height = Pitch*(Occurrences+0.5),
100    Rows = Occurrences+1,
101    make_thread(Type, Dir, Sections, Height, TopRadius, BotRadius, CrestH, Pitch, Rows, Modify).
102
103%%%
104%%% Thread
105%%%
106
107make_thread(Type, Dir, Sections, Height, TopRadius, BotRadius, CrestH, Pitch, Rows, [Rot, Mov, Ground]) ->
108    Vs1 = thread_verts(Type,Sections,TopRadius,BotRadius,CrestH,Pitch,Rows,Height),
109    Fs0 = thread_faces(Type,Sections,Rows),
110    {Vs0,Fs} = set_direction(Dir,Vs1,Fs0),
111    Vs = wings_shapes:transform_obj(Rot,Mov,Ground,Vs0),
112   {new_shape,thread(),Fs,Vs}.
113
114thread_verts(Type, Sections, TopRadius, BotRadius, CrestH, Pitch, Rows, Height) ->
115    RadRange = BotRadius-TopRadius,
116    RadInc = RadRange/(Rows-1),
117    Y = Height/2.0,
118    PitchInc = Pitch/2.0,
119    SubPitchInc = Pitch/Sections,
120    Delta = pi()*2/Sections,
121    Rings = lists:seq(Sections-1,0,-1),
122    lists:foldr(fun(Idx, Acc) ->
123                    YInc = Idx*Pitch,
124                    RInc = Idx*RadInc,
125                    Ring0 = ring_of_verts(Type, Rings, SubPitchInc, Delta, Y-YInc, TopRadius+RInc+CrestH),
126                    Ring1 = ring_of_verts(Type, Rings, SubPitchInc, Delta, Y-(YInc+PitchInc), TopRadius+RInc),
127                    Ring0++Ring1++Acc
128                end, [], lists:seq(0,Rows-1)).
129
130thread_faces(helicoid=Type, N, Rows) ->
131    R0 = (Rows-1)*2,
132    R = (Rows*2-1)*N,
133    Top = lists:seq(0,N-1),
134    Bottom = lists:seq(R+N-1,R, -1),
135    Sides = build_sides(Type,R0,N),
136    [Top, Bottom | Sides];
137thread_faces(non_helicoid=Type, N, Rows) ->
138    R0 = (Rows-1)*2,
139    R = (Rows*2-1)*N,
140    Top = lists:reverse(lists:seq(N-1,0,-1)),
141    Bottom = lists:seq(R+N-1,R, -1),
142    Sides = build_sides(Type,R0,N),
143    [Top, Bottom | Sides].
144
145build_sides(helicoid=Type, R, N) ->
146    Ns = lists:seq(0, N-2),
147    Fs = [[0, N-1, N],[N-1, 2*N, N]],
148    build_sides(Type,0,R,N,Ns,Fs);
149build_sides(non_helicoid=Type, R, N) ->
150    Ns = lists:seq(0, N-1),
151    build_sides(Type,0,R,N,Ns,[]).
152
153build_sides(_, Idx, R, _, _, Acc) when Idx > R ->
154    Acc;
155build_sides(helicoid=Type, Idx, R, N, Ns, Acc) ->
156    Sides = [[Idx*N + I, (Idx+1)*N + I,
157              (Idx+1)*N + ((I+1) rem N), Idx*N + ((Idx*N+I+1) rem N)] || I <- Ns],
158    if Idx < (R-1) ->
159        Stitch = [[Idx*N + N-1, (Idx+1)*N + N-1, (Idx+2)*N + N, (Idx+2)*N]];
160        Idx < R ->
161            Stitch = [[Idx*N + N-1, (Idx+1)*N + N-1, (Idx+2)*N]];
162        true ->
163            Stitch = [[Idx*N + N-1, (Idx+1)*N + N-1, (Idx+1)*N]]
164    end,
165    build_sides(Type,Idx+1,R,N,Ns,Acc++Sides++Stitch);
166build_sides(non_helicoid=Type, Idx, R, N, Ns, Acc) ->
167    Sides = [[Idx*N + I, (Idx+1)*N + I,
168              (Idx+1)*N + ((I+1) rem N), Idx*N + ((Idx*N+I+1) rem N)] || I <- Ns],
169    build_sides(Type,Idx+1,R,N,Ns,Acc++Sides).
170
171ring_of_verts(helicoid, Rings, YInc, Delta, YAxis, XZAxis) ->
172    [{XZAxis*cos(I*Delta), YAxis+YInc*I, XZAxis*sin(I*Delta)} || I <- Rings];
173ring_of_verts(non_helicoid, Rings, _, Delta, YAxis, XZAxis) ->
174    [{XZAxis*cos(I*Delta), YAxis, XZAxis*sin(I*Delta)} || I <- Rings].
175
176set_direction(left,Vs,Fs) ->
177    {Vs,Fs};
178set_direction(right,Vs0,Fs0) ->
179    FlipX = e3d_mat:scale(-1.0, 1.0, 1.0),
180    Vs = [e3d_mat:mul_point(FlipX, Pos) || Pos <- Vs0],
181    Fs = [lists:reverse(F) || F <- Fs0],
182    {Vs,Fs}.