1%%
2%%  wings_shapes.erl --
3%%
4%%     This module contains definition of all primitive
5%%     shapes that can be created, such as Cube, Sphere,
6%%     and Grid.
7%%
8%%  Copyright (c) 2001-2011 Bjorn Gustavsson
9%%
10%%  See the file "license.terms" for information on usage and redistribution
11%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12%%
13%%
14
15-module(wings_shapes).
16-export([menu/2,command/2]).
17-export([transform_obj_dlg/0, transform_obj/4, transform_obj/2]).
18-export([tri_sphere/1, tri_cube/1, tri_disc/1]).
19-include("wings.hrl").
20
21-import(lists, [map/2,seq/2,seq/3]).
22-import(math, [sqrt/1,cos/1,sin/1,pi/0]).
23
24menu(Parent, Pos) ->
25    wings_menu:popup_menu(Parent, Pos, shape, menu()).
26
27menu() ->
28    Opt = [option],
29    Menu0 = [
30	     {sphere,Opt},
31	     {cone,Opt},
32	     separator,
33	     {tetrahedron,Opt},
34	     {octahedron,Opt},
35	     {octotoad, Opt},
36	     {dodecahedron,Opt},
37	     {icosahedron,Opt},
38	     separator,
39	     {grid,Opt},
40	     separator,
41	     {prim_name(light),{light,wings_light:light_types()},
42	      prim_help(light)},
43	     material,
44	     image],
45    [prim_trans(Item) || Item <- Menu0].
46
47
48prim_trans(separator) ->
49    separator;
50prim_trans({K,Opts}) when is_atom(K) ->
51    {prim_name(K),K,prim_help(K),Opts};
52prim_trans(K) when is_atom(K) ->
53    {prim_name(K),K,prim_help(K)};
54prim_trans(Other) -> Other.
55
56prim_name(tetrahedron) ->  ?STR(prim_name,tetrahedron,"Tetrahedron");
57prim_name(octahedron) ->   ?STR(prim_name,octahedron,"Octahedron");
58prim_name(octotoad) ->     ?STR(prim_name,octotoad,"Octotoad");
59prim_name(dodecahedron) -> ?STR(prim_name,dodecahedron,"Dodecahedron");
60prim_name(icosahedron) ->  ?STR(prim_name,icosahedron,"Icosahedron");
61prim_name(cone) ->         ?STR(prim_name,cone,"Cone");
62prim_name(sphere) ->       ?STR(prim_name,sphere,"Sphere");
63prim_name(grid) ->         ?STR(prim_name,grid,"Grid");
64prim_name(light) ->        ?STR(prim_name,light,"Light");
65prim_name(material) ->     ?STR(prim_name,material,"Material...");
66prim_name(image) ->        ?STR(prim_name,image,"Image...").
67
68prim_help(tetrahedron) ->
69    ?STR(prim_help,tetrahedron,"Create a tetrahedron");
70prim_help(octahedron) ->
71    ?STR(prim_help,octahedron,"Create an octahedron");
72prim_help(octotoad) ->
73    ?STR(prim_help,octotoad,"Create an octotoad");
74prim_help(dodecahedron) ->
75    ?STR(prim_help,dodecahedron,"Create a dodecahedron");
76prim_help(icosahedron) ->
77    ?STR(prim_help,icosahedron,"Create an icosahedron");
78prim_help(cone) ->
79    ?STR(prim_help,cone,"Create a cone");
80prim_help(sphere) ->
81    ?STR(prim_help,sphere,"Create a sphere");
82prim_help(grid) ->
83    ?STR(prim_help,grid,"Create a grid");
84prim_help(light) ->
85    ?STR(prim_help,light,"Create a light");
86prim_help(material) ->
87    ?STR(prim_help,material,"Create a material...");
88prim_help(image) ->
89    ?STR(prim_help,image,"Create an image...").
90
91
92command({tetrahedron,Ask}, St) -> tetrahedron(Ask, St);
93command({octahedron,Ask}, St) -> octahedron(Ask, St);
94command({octotoad,Ask}, St) -> octotoad(Ask, St);
95command({dodecahedron,Ask}, St) -> dodecahedron(Ask, St);
96command({icosahedron,Ask}, St) -> icosahedron(Ask, St);
97command({cone,Ask}, St) -> cone(Ask, St);
98command({sphere,Ask}, St) -> sphere(Ask, St);
99command({grid,Ask}, St) -> grid(Ask, St);
100command({light,Type}, St) -> wings_light:create(Type, St);
101command(material, St) -> wings_material:new(St);
102command(image, St) -> wings_image:create(St).
103
104build_shape(Prefix, Fs, Vs, #st{onext=Oid}=St) ->
105    We = wings_we:build(Fs, Vs),
106    Name = Prefix++integer_to_list(Oid),
107    wings_obj:new(Name, We, St).
108
109tetrahedron(Ask, St) when is_atom(Ask) ->
110    Q = [{label_column, [
111	    {?STR(tetrahedron,1,"Edge Length"), {text, 2.0,[{range,{0.0,infinity}}]}}]
112	 },
113	 transform_obj_dlg()],
114  ask(tetrahedron, Ask, Q, St);
115tetrahedron([L|Transf], St) ->
116    Xi = L/2.0,
117    Hp = sqrt(3.0),
118    Li = Xi*Hp,
119    Zi = Xi/Hp,
120    Yi = L * sqrt(2.0/3.0),
121    Yf = Yi / 3.0,
122
123    Fs = [[2,1,0],[1,2,3],[1,3,0],[3,2,0]],
124    Vs0 = [{ 0.0,  Yi-Yf,  0.0},
125	   { 0.0, -Yf,   Li-Zi},
126	   { -Xi, -Yf,     -Zi},
127	   {  Xi, -Yf,     -Zi}],
128    %% The string below is intentionally not translated.
129    Vs = transform_obj(Transf, Vs0),
130    build_shape("tetrahedron", Fs, Vs, St).
131
132octahedron(Ask, St) when is_atom(Ask) ->
133    Q = [{label_column, [
134	{?STR(octahedron,1,"Height"), {text, 2.0,[{range,{0.0,infinity}}]}}]
135	 },
136	 transform_obj_dlg()],
137    ask(octahedron, Ask, Q, St);
138octahedron([L|Transf], St) ->
139    Fs = [[2,4,0],[4,2,1],[4,3,0],[3,4,1],[5,2,0],[2,5,1],[3,5,0],[5,3,1]],
140    Vs0 = [{L,0.0,0.0},{-L,0.0,0.0},{0.0,L,0.0},
141	   {0.0,-L,0.0},{0.0,0.0,L},{0.0,0.0,-L}],
142    Vs = transform_obj(Transf, Vs0),
143    %% The string below is intentionally not translated.
144    build_shape("octahedron", Fs, Vs, St).
145
146octotoad(Ask, St) when is_atom(Ask) ->
147    Q = [{label_column, [
148	{?STR(octotoad,1,"Height"), {text, 2.0,[{range,{0.0,infinity}}]}}]
149	 },
150	 transform_obj_dlg()],
151    ask(octotoad, Ask, Q, St);
152octotoad([L|Transf], St) ->
153    Fs = [[2,3,1,0],[7,6,4,5],[9,8,0,1],[10,11,3,2],
154	  [12,0,8],[12,14,2,0],[13,9,1],[14,10,2],
155	  [15,3,11],[15,13,1,3],[16,17,5,4],[16,20,12,8],
156	  [17,16,8,9],[18,19,11,10],[19,18,6,7],[19,23,15,11],
157	  [20,16,4],[20,22,14,12],[21,5,17],[21,17,9,13],
158	  [21,23,7,5],[22,6,18],[22,18,10,14],[22,20,4,6],
159	  [23,19,7],[23,21,13,15]],
160    Third = 1/3,
161    Vs1 = [{1.0,Third,Third},{1.0,Third,-Third},
162	   {1.0,-Third,Third},{1.0,-Third,-Third},
163	   {-1.0,Third,Third},{-1.0,Third,-Third},
164	   {-1.0,-Third,Third},{-1.0,-Third,-Third},
165	   {Third,1.0,Third},{Third,1.0,-Third},
166	   {Third,-1.0,Third},{Third,-1.0,-Third},
167	   {Third,Third,1.0},{Third,Third,-1.0},
168	   {Third,-Third,1.0},{Third,-Third,-1.0},
169	   {-Third,1.0,Third},{-Third,1.0,-Third},
170	   {-Third,-1.0,Third},{-Third,-1.0,-Third},
171	   {-Third,Third,1.0},{-Third,Third,-1.0},
172	   {-Third,-Third,1.0},{-Third,-Third,-1.0}],
173    Scale = (L/2.0),
174    Vs0 = [{X*Scale,Y*Scale,Z*Scale} || {X,Y,Z} <- Vs1],
175    Vs = transform_obj(Transf, Vs0),
176    %% The string below is intentionally not translated.
177    build_shape("octotoad", Fs, Vs, St).
178
179dodecahedron(Ask, St) when is_atom(Ask) ->
180    Q = [{label_column, [
181	    {?STR(dodecahedron,1,"Edge Length"), {text, 1.0,[{range,{0.0,infinity}}]}}
182	 ]},
183	 transform_obj_dlg()],
184    ask(dodecahedron, Ask, Q, St);
185dodecahedron([L|Transf], St) ->
186	 Pn = sqrt(5.0),
187	 Phi = (1.0 + Pn)/2.0,
188	 Li = L/2.0 * Phi,
189	 Ap = sqrt(2.0 / (3.0 + Pn)),
190	 Alpha = Li*Ap,
191    Beta = Li*(1.0 + sqrt(6.0 / (3.0 + Pn) - 2.0 + 2.0 * Ap)),
192    Fs = [[0,1,9,16,5],[1,0,3,18,7],[1,7,11,10,9],[11,7,18,19,6],
193	  [8,17,16,9,10],[2,14,15,6,19],[2,13,12,4,14],[2,19,18,3,13],
194	  [3,0,5,12,13],[6,15,8,10,11],[4,17,8,15,14],[4,12,5,16,17]],
195    Vs0 = [{-Alpha,0.0,Beta},{Alpha,0.0,Beta},{-Li,-Li,-Li},{-Li,-Li,Li},
196	   {-Li,Li,-Li},{-Li,Li,Li},{Li,-Li,-Li},
197	   {Li,-Li,Li},{Li,Li,-Li},
198	   {Li,Li,Li},{Beta,Alpha,0.0},{Beta,-Alpha,0.0},{-Beta,Alpha,0.0},
199	   {-Beta,-Alpha,0.0},{-Alpha,0.0,-Beta},{Alpha,0.0,-Beta},
200	   {0.0,Beta,Alpha},{0.0,Beta,-Alpha},{0.0,-Beta,Alpha},
201	   {0.0,-Beta,-Alpha}],
202    Vs = transform_obj(Transf, Vs0),
203    %% The string below is intentionally not translated.
204    build_shape("dodecahedron", Fs, Vs, St).
205
206icosahedron(Ask, St) when is_atom(Ask) ->
207    Q = [{label_column, [
208	    {?STR(icosahedron,1,"Edge Length"), {text,2.0,[{range,{0.0,infinity}}]}}
209	 ]},
210	 transform_obj_dlg()],
211    ask(icosahedron, Ask, Q, St);
212icosahedron([S|Transf], St) ->
213    T2 = pi()/10.0,
214    T4 = 2.0 * T2,
215    CT4 = cos(T4),
216    R  = (S/2.0) / sin(T4),
217    H  = CT4 * R,
218    Cx = R * cos(T2),
219    Cy = R * sin(T2),
220    H1 = sqrt(S * S - R * R),
221    H2 = abs(R) * sqrt(1.0 + 2.0*CT4),
222    Z2 = (H2 - H1) / 2.0,
223    Z1 = Z2 + H1,
224
225    Fs = [[0,2,1],[0,3,2],[0,4,3],[0,5,4],[0,1,5],
226	  [4,7,6],[2,9,1],[5,8,7],[3,10,2],[9,8,1],
227	  [4,6,3],[10,9,2],[7,4,5],[6,10,3],[1,8,5],
228	  [11,10,6],[11,6,7],[11,7,8],[11,8,9],[11,9,10]],
229
230    Vs0 =[{0.0,  Z1, 0.0},
231      {R,    Z2, 0.0	},
232      {Cy,   Z2, Cx	},
233      {-H,   Z2, S/2.0	},
234      {-H,   Z2, -S/2.0	},
235      {Cy,   Z2, -Cx 	},
236      {-R,  -Z2, 0.0	},
237      {-Cy, -Z2, -Cx	},
238      { H,  -Z2, -S/2.0	},
239      { H,  -Z2, S/2.0	},
240      {-Cy, -Z2, Cx	},
241      {0.0, -Z1, 0.0	}],
242    Vs = transform_obj(Transf, Vs0),
243    %% The string below is intentionally not translated.
244    build_shape("icosahedron", Fs, Vs, St).
245
246circle(N, Y, R) ->
247    Delta = pi()*2 / N,
248    [{R*cos(I*Delta), Y, R*sin(I*Delta)} || I <- lists:seq(0, N-1)].
249
250ellipse(N, Y, {R1, R2}) ->
251    Delta = pi()*2 / N,
252    [{R1*cos(I*Delta), Y, R2*sin(I*Delta)} || I <- lists:seq(0, N-1)].
253
254cone(Ask, St) when is_atom(Ask) ->
255    Q = [{label_column, [
256	    {?STR(cone,1,"Sections"), {text,16,[{range,{3,infinity}}]}},
257	    {?STR(cone,2,"Height"), {text,2.0,[{range,{0.0,infinity}}]}},
258	    {?STR(cone,3,"X Diameter"), {text,2.0,[{range,{0.0,infinity}}]}},
259	    {?STR(cone,5,"Z Diameter"), {text,2.0,[{range,{0.0,infinity}}]}}
260	 ]},
261	 transform_obj_dlg()],
262    ask(cone, Ask, Q, St);
263cone([N,H,Dx,Dy|Transf], St) ->
264    Hi = H/2.0,
265	 Di = Dx/2.0,
266	 Dj = Dy/2.0,
267	 Ns = lists:seq(0, N-1),
268    Lower = lists:seq(0, N-1),
269    C = ellipse(N, -Hi, {Di, Dj}),
270    Vs0 = C ++ [{0.0,Hi,0.0}],
271    Vs = transform_obj(Transf, Vs0),
272    Sides = [[N, (I+1) rem N, I] || I <- Ns],
273    Fs = [Lower | Sides],
274    %% The string below is intentionally not translated.
275    build_shape("cone", Fs, Vs, St).
276
277sphere_circles(Ns, Nl, Xi, Yi) ->
278    Delta = pi() / Nl,
279    PosAndRads= [{cos(I*Delta), sin(I*Delta)} || I <- lists:seq(1, Nl-1)],
280    Circles = [circle(Ns, Pos*Xi, Rad*Yi) || {Pos, Rad} <- PosAndRads],
281    lists:flatten(Circles).
282
283sphere_faces(Ns, Nl) ->
284    Lasti= (Nl-1)*Ns,
285    Topi= Lasti,
286    Boti= Topi+1,
287    Topf= [[Topi, (I+1) rem Ns, I]
288	   || I <- lists:seq(0, Ns-1)],
289    Botf= [[Boti, Lasti - Ns + I, Lasti - Ns + (I+1) rem Ns]
290	   || I <- lists:seq(0, Ns-1)],
291    Slices= [ [ [(I+1) rem Ns  +J*Ns,
292		 (I+1) rem Ns  +J*Ns +Ns,
293		 I             +J*Ns +Ns,
294		 I             +J*Ns]
295		|| I <- lists:seq(0, Ns-1)]
296	      || J <- lists:seq(0, Nl-3)],
297    Topf ++ Botf ++ lists:append(Slices).
298
299sphere(Ask, St) when is_atom(Ask) ->
300    Q = [{label_column, [
301	    {?STR(sphere,1,"Sections"), {text,16,[{range,{3,infinity}}]}},
302	    {?STR(sphere,2,"Slices"), {text,8,[{range,{3,infinity}}]}},
303	    {?STR(sphere,3,"X Radial"), {text,2.0,[{range,{0.0,infinity}}]}},
304	    {?STR(sphere,4,"Y Radial"), {text,2.0,[{range,{0.0,infinity}}]}}
305    	 ]},
306	 transform_obj_dlg()],
307    ask(sphere, Ask, Q, St);
308sphere([Ns,Nl,Xr,Yr|Transf], St) ->
309    Xi = Xr/2.0,
310    Yi = Yr/2.0,
311    Fs = sphere_faces(Ns, Nl),
312    Vs0 = sphere_circles(Ns, Nl, Xi, Yi) ++ [{0.0, Xi, 0.0}, {0.0, -Xi, 0.0}],
313    Vs = transform_obj(Transf, Vs0),
314    %% The string below is intentionally not translated.
315    build_shape("sphere", Fs, Vs, St).
316
317grid(Ask, St) when is_atom(Ask) ->
318    Q = [{label_column, [
319	    {?STR(grid,1,"Rows/Cols"), {text,10,[{range,{1,infinity}}]}},
320	    {?STR(grid,2,"X Spacing"), {text,0.5,[{range,{0.0,infinity}}]}},
321	    {?STR(grid,3,"Z Spacing"), {text,0.5,[{range,{0.0,infinity}}]}},
322	    {?STR(grid,4,"Height"), {text,0.1,[{range,{0.0,infinity}}]}}
323    	 ]},
324	 transform_obj_dlg()],
325    ask(grid, Ask, Q, St);
326grid([Rc,Sp,Zp,Ht|Transf], St) ->
327    Vs0 = grid_vertices(Rc,Sp,Zp,Ht),
328    Vs = transform_obj(Transf, Vs0),
329    Fs = grid_faces(Rc),
330    %% The string below is intentionally not translated.
331    build_shape("grid", Fs, Vs, St).
332
333grid_vertices(Rc,Sp,Zp,Ht) ->
334    {Low,High} = case Rc rem 2 of
335		     0 -> {-Rc,Rc};
336		     1 -> {-Rc,Rc}
337		 end,
338    TopSeq = seq(Low, High, 2),
339    BotSeq = [Low,High],
340    VsBot = [{I*Sp/2,-Ht,J*Zp/2} || J <- BotSeq, I <- BotSeq],
341    [{I*Sp/2,Ht,J*Zp/2} || J <- TopSeq, I <- TopSeq] ++ VsBot.
342
343grid_faces(Rc) ->
344    TopSeq = seq(0, Rc-1),
345    Rsz = Rc+1,
346    TopFs = [grid_face(I, J, Rsz) || I <- TopSeq, J <- TopSeq],
347    ULv = Rsz*Rsz,
348    Fs0 = [[ULv,ULv+1,ULv+3,ULv+2]|TopFs],	%Add bottom.
349    Fs1 = [[ULv+1,ULv|seq(0, Rsz-1)]|Fs0],	%North
350    Fs2 = [[ULv,ULv+2|seq(Rsz*Rsz-Rsz, 0, -Rsz)]|Fs1], %West
351    Fs = [[ULv+2,ULv+3|seq(Rsz*Rsz-1, Rsz*Rsz-Rsz, -1)]|Fs2], % South.
352    [[ULv+3,ULv+1|seq(Rsz-1, Rsz*Rsz-1, Rsz)]|Fs]. %East
353
354grid_face(I, J, Rsz) ->
355    [Rsz*J+I+1,   Rsz*J+I,
356     Rsz*(J+1)+I, Rsz*(J+1)+I+1].
357
358ask(Shape, Bool, Qs, St) ->
359    Title = prim_help(Shape),
360    wings_dialog:dialog_preview({shape,Shape}, Bool, Title, Qs, St).
361
362transform_obj_dlg() ->
363    Hook = fun(Var, Val, Sto) ->
364	case Var of
365	    ground ->
366		wings_dialog:enable(mov_y, Val=:=false, Sto);
367	    _ -> ok
368	end
369	   end,
370    {vframe,[
371	{hframe,[
372	    {label_column,
373	     [{wings_util:stringify(rotate),
374	       {label_column, [
375		   {wings_util:stringify(x),{text, 0.0,[{key,rot_x},{range,{-360.0,360.0}}]}},
376		   {wings_util:stringify(y),{text, 0.0,[{key,rot_y},{range,{-360.0,360.0}}]}},
377		   {wings_util:stringify(z),{text, 0.0,[{key,rot_z},{range,{-360.0,360.0}}]}}
378	       ]}
379	      }
380	     ]},
381	    {label_column,
382	     [{wings_util:stringify(move),
383	       {label_column, [
384		   {wings_util:stringify(x),{text, 0.0,[{key,mov_x},{range,{-360.0,360.0}}]}},
385		   {wings_util:stringify(y),{text, 0.0,[{key,mov_y},{range,{-360.0,360.0}}]}},
386		   {wings_util:stringify(z),{text, 0.0,[{key,mov_z},{range,{-360.0,360.0}}]}}
387	       ]}
388	      }]}
389	],[{margin,false}]},
390	{wings_util:stringify(put_on_ground), false, [{key,ground},{hook, Hook}]}
391    ],[{title,""},{margin,false}]}.
392
393transform_obj([{_,Rot_X},{_,Rot_Y},{_,Rot_Z},{_,Mov_X},{_,Mov_Y},{_,Mov_Z},{_,Ground}], Vs) ->
394    transform_obj({Rot_X,Rot_Y,Rot_Z}, {Mov_X,Mov_Y,Mov_Z}, Ground, Vs).
395transform_obj({0.0,0.0,0.0},{0.0,0.0,0.0},false, Vs) -> Vs;
396transform_obj({Rot_X,Rot_Y,Rot_Z}, {Mov_X,Mov_Y,Mov_Z}, Ground, Vs) ->
397    MrX = e3d_mat:rotate(Rot_X, {1.0,0.0,0.0}),
398    MrY = e3d_mat:rotate(Rot_Y, {0.0,1.0,0.0}),
399    MrZ = e3d_mat:rotate(Rot_Z, {0.0,0.0,1.0}),
400    Mr = e3d_mat:mul(MrZ, e3d_mat:mul(MrY, MrX)),
401    Y = case  Ground of
402	    true ->
403		{{_,Y1,_},{_,Y2,_}} = e3d_bv:box(Vs),
404		min(Y1,Y2)*-1.0;
405	    false -> Mov_Y
406	end,
407    Mt = e3d_mat:translate(Mov_X,Y,Mov_Z),
408    Mat = e3d_mat:mul(Mt,Mr),
409    [e3d_mat:mul_point(Mat, V) || V <- Vs].
410
411
412%%% Other shapes for internal rendering
413%% Replaces glu quadric functionality
414%% Returns the number of triangles and the triangles in a list
415%% or in a binary if option binary is true.
416%% Extra contents depends on Options.
417%% Options:
418%%     subd    Subdivision level default 1
419%%     binary  All output is binary default false
420%%     ccw     Winding order counter clockwise default true
421%%     scale   Scale output default 1,
422%%     normals Add normals to the default false
423%%     tgs     Add tangent-normals to the extra list default false
424
425-type out() :: binary() | list().
426
427-define(LLF, {-1.0, -1.0,  1.0}).  %% Lower left front
428-define(LLB, {-1.0, -1.0, -1.0}).  %% Lower left back
429-define(LRF, { 1.0, -1.0,  1.0}).  %% Lower Right front
430-define(LRB, { 1.0, -1.0, -1.0}).
431-define(ULF, {-1.0,  1.0,  1.0}).  %% Upper Left Front
432-define(ULB, {-1.0,  1.0, -1.0}).
433-define(URF, { 1.0,  1.0,  1.0}).
434-define(URB, { 1.0,  1.0, -1.0}).
435
436%%           +Y
437%%      ULB  _____ URB
438%%         /| -Z /|
439%%   ULF  /_____/ URF
440%% -X LLB|->|__ |_| LRB   +X
441%%       | /    | /
442%%   LLF |/_____|/ LRF
443%%         +Z
444-define(cube,
445	[{?LLF, ?LRF, ?URF}, {?URF,  ?ULF, ?LLF},  % Front
446         {?LRB, ?LLB, ?ULB}, {?ULB,  ?URB, ?LRB},  % Back
447         {?ULF, ?URF, ?URB}, {?URB,  ?ULB, ?ULF},  % Top
448         {?LLB, ?LRB, ?LRF}, {?LRF,  ?LLF, ?LLB},  % Bottom
449         {?LLB, ?LLF, ?ULF}, {?ULF,  ?ULB, ?LLB},  % Left
450         {?LRF, ?LRB, ?URB}, {?URB,  ?URF, ?LRF}   % Right
451        ]).
452
453-spec tri_cube(Opts::map()) ->
454          #{size=>NoOfFs::integer(), tris := Tris::out(), ns => Normals::out(), uvs := UVs::out(), tgs := Tgs::out()}.
455tri_cube(Opts) ->
456    Binary = maps:get(binary, Opts, false),
457    CCW    = maps:get(ccw, Opts, true),
458    Scale  = maps:get(scale, Opts, 1),
459    (maps:get(subd, Opts, 1) > 1) andalso error(not_yet_implented),
460    maps:get(normals, Opts, false) andalso error(not_yet_implented),
461    maps:get(uvs, Opts, false) andalso error(not_yet_implented),
462    maps:get(tgs, Opts, false) andalso error(not_yet_implented),
463
464    Tris = ?cube,
465    convert(Binary, Tris, CCW, Scale, false, false, false).
466
467-define(XPLUS, {1.0,0.0,0.0}).
468-define(XMIN, {-1.0,0.0,0.0}).
469-define(YPLUS, {0.0,1.0,0.0}).
470-define(YMIN, {0.0,-1.0,0.0}).
471-define(ZPLUS, {0.0,0.0,1.0}).
472-define(ZMIN, {0.0,0.0,-1.0}).
473-define(ZERO, {0.0,0.0,0.0}).
474-define(octahedron,
475	[{?ZPLUS, ?XPLUS, ?YPLUS},
476	 {?XMIN,  ?ZPLUS, ?YPLUS},
477	 {?ZPLUS, ?XMIN,  ?YMIN },
478	 {?ZPLUS, ?YMIN,  ?XPLUS},
479	 {?YPLUS, ?XPLUS, ?ZMIN },
480	 {?XMIN,  ?YPLUS, ?ZMIN },
481	 {?YMIN,  ?XMIN,  ?ZMIN },
482	 {?XPLUS, ?YMIN,  ?ZMIN }]).
483
484-spec tri_sphere(Opts :: map()) ->
485          #{size := integer(), tris := out(), ns := out(), uvs := out(), tgs := out()}.
486tri_sphere(Opts) when is_map(Opts) ->
487    Subd   = maps:get(subd, Opts, 1),
488    Binary = maps:get(binary, Opts, false),
489    CCW    = maps:get(ccw, Opts, true),
490    Scale  = maps:get(scale, Opts, 1),
491    Normal = maps:get(normals, Opts, false),
492    UV     = maps:get(uvs, Opts, false),
493    Tg     = maps:get(tgs, Opts, false),
494    %% Do the work
495    Tris   = subd_sphere(1, Subd, ?octahedron),
496    convert(Binary, Tris, CCW, Scale, Normal, UV, Tg).
497
498-define(diamond,
499        [{?ZERO, ?YPLUS, ?XMIN},
500         {?ZERO, ?XMIN, ?YMIN},
501         {?ZERO, ?YMIN, ?XPLUS},
502         {?ZERO, ?XPLUS, ?YPLUS}]).
503-spec tri_disc(Opts :: map()) ->
504          #{size := integer(), tris := out(), ns := out(), uvs := out(), tgs := out()}.
505tri_disc(Opts) ->
506    Binary = maps:get(binary, Opts, false),
507    CCW    = maps:get(ccw, Opts, true),
508    Scale  = maps:get(scale, Opts, 1),
509    Subd   = maps:get(subd, Opts, 1),
510
511    maps:get(normals, Opts, false) andalso error(not_yet_implented),
512    maps:get(uvs, Opts, false) andalso error(not_yet_implented),
513    maps:get(tgs, Opts, false) andalso error(not_yet_implented),
514    Tris = subd_disc(1, Subd, ?diamond),
515    convert(Binary, Tris, CCW, Scale, false, false, false).
516
517subd_sphere(Level, MaxLevel, Sphere0) when Level < MaxLevel ->
518    Sphere = subd_sphere(Sphere0, []),
519    subd_sphere(Level+1, MaxLevel, Sphere);
520subd_sphere(_,_, Sphere) -> Sphere.
521
522%%	  2             create a, b, c in the middle
523%%	 /\		Normalize a, b, c
524%%	/  \
525%%    c/____\ b		Construct new triangles
526%%    /\    /\		    [0,b,a]
527%%   /	\  /  \		    [a,b,c]
528%%  /____\/____\	    [a,c,2]
529%% 0	  a	1	    [b,1,c]
530%%
531
532subd_sphere([{V0,V1,V2}|Rest], Acc) ->
533    A = e3d_vec:norm(midpoint(V0,V1)),
534    B = e3d_vec:norm(midpoint(V1,V2)),
535    C = e3d_vec:norm(midpoint(V0,V2)),
536    T1 = {V0,A,C},
537    T2 = {A,B,C},
538    T3 = {A,V1,B},
539    T4 = {C,B,V2},
540    subd_sphere(Rest, [T1,T2,T3,T4|Acc]);
541subd_sphere([],Acc) ->
542    Acc.
543
544midpoint({X1,Y1,Z1}, {X2,Y2,Z2}) ->
545    {(X1+X2)*0.5, (Y1+Y2)*0.5, (Z1+Z2)*0.5}.
546
547
548subd_disc(Level, Max, Tris0) when Level < Max ->
549    subd_disc(Level+1, Max, subd_disc(Tris0, []));
550subd_disc(_, _, Tris) ->
551    Tris.
552
553subd_disc([{C,V1,V2}|Rest], Acc) ->
554    M = e3d_vec:norm(midpoint(V1,V2)),
555    subd_disc(Rest, [{C,V1,M}, {C,M,V2}|Acc]);
556subd_disc([], Acc) ->
557    Acc.
558
559convert(true, Tris, CCW, Scale, Normal, UV, Tg) ->
560    BinTris = list_to_bin(Tris, CCW, Scale),
561    Ns = if not Normal -> <<>>;
562            Scale =:= 1 -> [BinTris];
563            true -> [list_to_bin(Tris, CCW, 1)]
564         end,
565    UVs = if not UV -> <<>>;
566             true -> list_to_bin(prepare_uvs(Tris), CCW, 1)
567          end,
568    Tgs = if not Tg -> <<>>;
569             true -> list_to_bin(prepare_tgs(Tris), CCW, 1)
570          end,
571    #{size => size(BinTris) div (9*4), tris => BinTris, ns => Ns, uvs => UVs, tgs => Tgs};
572convert(false, Tris, CCW, Scale, Normal, UV, Tg) ->
573    Scaled = convert_list(Tris, CCW, Scale),
574    Ns = if not Normal -> [];
575            Scale =:= 1 -> Scaled;
576            true -> convert_list(Tris, CCW, 1)
577         end,
578    UVs = if not UV -> [];
579             true -> convert_list(prepare_uvs(Tris), CCW, 1)
580          end,
581    Tgs = if not Tg -> [];
582             true -> convert_list(prepare_tgs(Tris), CCW, 1)
583          end,
584    #{size => length(Tris), tris => Scaled, ns => Ns, uvs => UVs, tgs => Tgs}.
585
586
587%%%%%%%%%%%%%% Compute tangents for tri_sphere %%%%%%%%%%%
588prepare_tgs(Tris) ->
589    lists:foldr(fun({A,B,C}, Acc) ->
590                        [{calc_tg(A),calc_tg(B),calc_tg(C)}|Acc]
591		end, [], Tris).
592
593calc_tg(?YPLUS) -> {0.0,0.0,-1.0,-1.0};
594calc_tg(?YMIN) -> {0.0,0.0,-1.0,-1.0};
595calc_tg(N) ->
596    Bi = e3d_vec:cross(N,{0.0,1.0,0.0}),
597    T = {X,Y,Z} = e3d_vec:norm(e3d_vec:cross(N, Bi)),
598    case e3d_vec:dot(e3d_vec:cross(N, T), Bi) < 0.0 of
599        true -> {X,Y,Z,1.0};
600        false -> {X,Y,Z,-1.0}
601    end.
602
603%%%%%%%%%%%%%% Compute the UVs for tri_sphere %%%%%%%%%%%
604prepare_uvs(Tris) ->
605    lists:foldr(fun({A,B,C}, Acc) ->
606                        {UA1,VA} = calc_uv(A),
607                        {UB1,VB} = calc_uv(B),
608                        {UC1,VC} = calc_uv(C),
609                        UA0 = close_uv_loop(UA1,UB1,UC1),
610                        UB0 = close_uv_loop(UB1,UC1,UA1),
611                        UC0 = close_uv_loop(UC1,UA1,UB1),
612                        UA = fix_top_issue(A, UA0, UB0, UC0),
613                        UB = fix_top_issue(B, UB0, UC0, UA0),
614                        UC = fix_top_issue(C, UC0, UA0, UB0),
615                        [{{UA,VA},{UB,VB},{UC,VC}}|Acc]
616		end, [], Tris).
617
618calc_uv({X,Y,Z}) ->
619    U = 0.5 + math:atan2(X, Z) / (2 * math:pi()),
620    V = 0.5 + math:asin(Y) / math:pi(),
621    {U,V}.
622
623fix_top_issue(V, _, UB, UC) when V=:=?YPLUS; V=:=?YMIN -> (UB+UC)/2.0;
624fix_top_issue(_, UA, _, _) -> UA.
625
626close_uv_loop(1.0, UVuB, UVuC) when (UVuB < 0.5); (UVuC < 0.5) -> 0.0;
627close_uv_loop(UVuA, _, _) -> UVuA.
628
629%%%%%%%%%%%%%% Converters %%%%%%%%%%%
630list_to_bin([{{_,_},_,_}|_]=Tris, CCW, Scale) ->
631    << <<(U):?F32,(V):?F32>> || Fs <- Tris, {U,V} <- conv_tuple_bin(Fs,CCW,Scale) >>;
632list_to_bin(Tris, CCW, Scale) ->
633    << <<(X):?F32,(Y):?F32,(Z):?F32>> || Fs <- Tris, {X,Y,Z} <- conv_tuple_bin(Fs,CCW,Scale) >>.
634
635conv_tuple_bin(Fs, CCW, Size) ->
636    [scale(V, Size) || V <- conv_tuple_list(Fs,CCW)].
637
638convert_list(List, CCW, 1) ->
639    [V || Fs <- List, V <- conv_tuple_list(Fs,CCW)];
640convert_list(List, CCW, Size) ->
641    [scale(V, Size) || Fs <- List, V <- conv_tuple_list(Fs,CCW)].
642
643conv_tuple_list({V1,V2,V3}, true) -> [V1,V2,V3];
644conv_tuple_list({V1,V2,V3}, false) -> [V1,V3,V2].
645
646scale({X,Y,Z},Size) -> {(X*Size),(Y*Size),(Z*Size)};
647scale({_,_}=UV,_) -> UV.
648