1%%
2%%  wings_material.erl --
3%%
4%%     This module manages the face materials (i.e. colors and textures).
5%%
6%%  Copyright (c) 2001-2011 Bjorn Gustavsson
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%%     $Id$
12%%
13
14-module(wings_material).
15-export([material_menu/1,command/2,new/1,color/4,default/0,
16	 add_materials/2,add_materials/3,
17	 update_materials/2,
18	 update_image/4,used_images/1,
19	 used_materials/1,has_texture/2,
20	 apply_material/4,is_transparent/2,
21	 needed_attributes/2,
22         specular_to_metal/1, specular_from_metal/1
23        ]).
24
25-define(NEED_OPENGL, 1).
26-define(NEED_ESDL, 1).
27-include("wings.hrl").
28-include_lib("e3d/e3d_image.hrl").
29
30-import(lists, [sort/1,foldl/3,reverse/1,
31		keyreplace/4,keydelete/3,keyfind/3,flatten/1]).
32
33-define(DEF_METALLIC, 0.1).
34-define(DEF_ROUGHNESS, 0.8).
35
36material_menu(St) ->
37    [{?__(4,"Material"),{material,material_fun(St)}}].
38
39material_fun(St) ->
40    fun(help, _Ns) ->
41	    {?__(1,"Assign existing material to selection"),
42	     ?__(3,"Edit material assigned to selection"),
43	     ?__(2,"Create and assign new material")};
44       (1, _Ns) ->
45	    mat_list(St);
46       (2, _Ns) ->
47	    mat_used_list(St);
48       (3, _) ->
49	    {material,new};
50       (_, _) -> ignore
51    end.
52
53mat_list(#st{mat=Mtab}) ->
54    mat_list_1(gb_trees:to_list(Mtab), []).
55
56mat_list_1([{Name,Ps}|Ms], Acc) ->
57    OpenGL = prop_get(opengl, Ps, []),
58    Diff = prop_get(diffuse, OpenGL),
59    Menu = {atom_to_list(Name),{assign,Name},[],[{color,Diff}]},
60    mat_list_1(Ms, [Menu|Acc]);
61mat_list_1([], Acc) -> reverse(Acc).
62
63mat_used_list(St) ->
64    MatList =
65        wings_sel:fold(fun(Sel,We,Acc) ->
66            gb_sets:fold(fun(F,Acc0)->
67                MatName = atom_to_list(wings_facemat:face(F,We)),
68                case lists:member(MatName,Acc0) of
69                    false -> [MatName|Acc0];
70                    true -> Acc0
71                end
72            end, Acc, Sel)
73        end, [], St),
74    case length(MatList) of
75        1 -> {material,{edit,lists:nth(1,MatList)}};
76        _ -> [{MatName,{'VALUE',{material,{edit,MatName}}},[]} || MatName <- MatList]
77    end.
78
79new(_) ->
80    new_1(new).
81
82new_1(Act) ->
83    wings_dialog:ask(?__(1,"New Material"),
84		     [{?__(2,"Material Name"),?__(3,"New Material")}],
85		     fun([Name]) ->
86			     Action = {action,{material,{Act,Name}}},
87			     wings_wm:send_after_redraw(geom, Action),
88			     ignore
89		     end).
90
91command(new, _) ->
92    new_1(assign_new);
93command({assign_new,Name}, St) ->
94    new_material(Name, true, St);
95command({new,Name}, St) ->
96    new_material(Name, false, St);
97command({edit,Mat}, St) ->
98    edit(list_to_atom(Mat), false, St);
99command({{assign,Mat},true}, St) -> % it handles the opt color icon
100    command({assign,Mat}, St);
101command({assign,Mat}, St) when is_atom(Mat) ->
102    set_material(Mat, St);
103command({assign,Mat}, St) ->
104    set_material(list_to_atom(Mat), St);
105command({select,[Mat,SelAct]}, St) ->
106    {save_state,select_material(list_to_atom(Mat),SelAct, St)};
107command({duplicate,MatList}, St) ->
108    duplicate_material(MatList, St);
109command({delete,MatList}, St) ->
110    delete_material(MatList, St);
111command({rename, Old, New}, St) ->
112    rename_1([{list_to_atom(Old),New}], St, []);
113command({rename,MatList0}, St) ->
114    case MatList0 -- ["default"] of
115	[] -> St;
116	MatList -> rename(MatList, St)
117    end;
118command({update,Name,Mat}, #st{mat=Mtab0}=St) ->
119    Mtab = gb_trees:update(Name, Mat, Mtab0),
120    St#st{mat=Mtab}.
121
122new_material(Name0, Assign, #st{mat=Mtab}=St) ->
123    Name1 = list_to_atom(Name0),
124    case gb_trees:is_defined(Name1, Mtab) of
125	true ->
126	    Names = [atom_to_list(N) || N <- gb_trees:keys(Mtab)],
127	    Name = list_to_atom(wings_util:unique_name(Name0, Names)),
128	    new_material_1(Name, Assign, St);
129	false ->
130	    new_material_1(Name1, Assign, St)
131    end.
132
133new_material_1(Name, Assign, St0) ->
134    Mat = make_default({1.0,1.0,1.0}, 1.0),
135    St = add(Name, Mat, St0),
136    edit(Name, Assign, St).
137
138duplicate_material([M0|Ms], #st{mat=Mat}=St0) ->
139    M1 = list_to_atom(M0),
140    MatPs = gb_trees:get(M1, Mat),
141    M = new_name(M0, Mat),
142    St = add(M, MatPs, St0),
143    duplicate_material(Ms, St);
144duplicate_material([], St) -> St.
145
146delete_material(["default"|Ms], St) ->
147    delete_material(Ms, St);
148delete_material([M0|Ms], #st{mat=Mat0}=St0) ->
149    M = list_to_atom(M0),
150    Mat = gb_trees:delete(M, Mat0),
151    St = reassign_material(M, default, St0),
152    delete_material(Ms, St#st{mat=Mat});
153delete_material([], St) ->
154    {save_state,St}.
155
156rename(Mats, St) ->
157    Qs = rename_qs(Mats),
158    wings_dialog:dialog(?__(1,"Rename"), Qs,
159			fun([{_,[]}]) -> ignore;
160			   (NewNames) ->
161				rename_1(NewNames, St, [])
162			end).
163
164rename_1([{Old,New}|Ms], #st{mat=Mat0}=St, Acc) ->
165    MatPs = gb_trees:get(Old, Mat0),
166    Mat = gb_trees:delete(Old, Mat0),
167    rename_1(Ms, St#st{mat=Mat}, [{Old,list_to_atom(New),MatPs}|Acc]);
168rename_1([], St, Acc) -> rename_2(Acc, St).
169
170rename_2([{Old,New0,MatPs}|Ms], St0) ->
171    case add(New0, MatPs, St0) of
172	{St1,New} -> ok;
173	#st{}=St1 -> New = New0
174    end,
175    St = reassign_material(Old, New, St1),
176    rename_2(Ms, St);
177rename_2([], St) -> St.
178
179rename_qs(Ms) ->
180    OldNames = [{label,M} || M <- Ms],
181    TextFields = [{text,M,[{key,list_to_atom(M)},{width,22}]} || M <- Ms],
182    [{hframe,
183      [{vframe,OldNames},
184       {vframe,TextFields}]}].
185
186reassign_material(Old, New, St) ->
187    SF = set_material_fun(New, face),
188    F = fun(We) ->
189                Faces = faces_with_mat(Old, We),
190                case gb_sets:is_empty(Faces) of
191                    false -> SF(Faces, We);
192                    true -> We
193                end
194        end,
195    wings_obj:we_map(F, St).
196
197faces_with_mat(OldMat, #we{fs=Ftab,mat=OldMat}) ->
198    gb_sets:from_ordset(gb_trees:keys(Ftab));
199faces_with_mat(_, #we{mat=Atom}) when is_atom(Atom) ->
200    gb_sets:empty();
201faces_with_mat(OldMat, #we{mat=MatTab}) ->
202    Fs = [Face || {Face,Mat} <- MatTab, Mat =:= OldMat],
203    gb_sets:from_ordset(Fs).
204
205select_material(Mat, SelAct, #st{selmode=Mode}=St) ->
206    F = select_fun(SelAct, Mat, Mode),
207    case SelAct of
208        select ->
209            wings_sel:new_sel(F, Mode, St);
210        sel_add ->
211            wings_sel:update_sel_all(F, St);
212        sel_rem ->
213            wings_sel:update_sel(F, St)
214    end.
215
216select_fun(select, Mat, Mode) ->
217    fun(_, We) ->
218            selected_items(Mode, Mat, We)
219    end;
220select_fun(sel_add, Mat, Mode) ->
221    fun(Items0, We) ->
222            Items = selected_items(Mode, Mat, We),
223            gb_sets:union(Items0, Items)
224    end;
225select_fun(sel_rem, Mat, Mode) ->
226    fun(Items0, We) ->
227            Items = selected_items(Mode, Mat, We),
228            gb_sets:difference(Items0, Items)
229    end.
230
231%% Select the elements (face/edge/vertice) using Mat
232selected_items(Mode, Mat, #we{fs=Ftab}=We) ->
233    MatFaces = wings_facemat:mat_faces(gb_trees:to_list(Ftab), We),
234    case keyfind(Mat, 1, MatFaces) of
235	false ->
236	    gb_sets:empty();
237        _ when Mode =:= body ->
238            gb_sets:singleton(0);
239	{Mat,FaceInfoList} ->
240	    Fs = [F || {F,_} <- FaceInfoList, F >= 0],
241            SelItems = case Mode of
242                           vertex -> wings_face:to_vertices(Fs, We);
243                           edge -> wings_face:to_edges(Fs, We);
244                           face -> Fs
245                       end,
246            gb_sets:from_ordset(SelItems)
247    end.
248
249set_material(Mat, #st{selmode=Mode}=St) ->
250    F = set_material_fun(Mat, Mode),
251    wings_sel:map(F, St).
252
253set_material_fun(Mat, face) ->
254    fun(Faces, We) ->
255            wings_facemat:assign(Mat, Faces, We)
256    end;
257set_material_fun(Mat, body) ->
258    fun(_, #we{fs=Ftab}=We) ->
259            wings_facemat:assign(Mat, gb_trees:keys(Ftab), We)
260    end;
261set_material_fun(_, _) ->
262    fun(_, We) -> We end.
263
264default() ->
265    Dm = wings_pref:get_value(material_default),
266    M = [{default,make_default(Dm, 1.0, [{vertex_colors,set}])}],
267    gb_trees:from_orddict(sort(M)).
268
269make_default(Color, Opacity) ->
270    make_default(Color, Opacity, []).
271
272make_default({R,G,B}, Opacity, More) ->
273    Color = {R,G,B,Opacity},
274    Dark = {0.0,0.0,0.0,1.0},
275    Mat = [{opengl,[{diffuse,Color}, {metallic, ?DEF_METALLIC},
276                    {roughness, ?DEF_ROUGHNESS}, {emission,Dark}|More]},
277	   {maps,[]}],
278    sort([{K,sort(L)} || {K,L} <- Mat]).
279
280update_image(MatName, MapType, Image, #st{mat=Mtab}) ->
281    Mat = gb_trees:get(MatName, Mtab),
282    Maps = prop_get(maps, Mat, []),
283    {MapType,ImageId} = keyfind(MapType, 1, Maps),
284    ok = wings_image:update(ImageId, Image).
285
286add_materials(Ms, St) ->
287    Dir = wings_pref:get_value(current_directory),
288    add_materials_1(Ms, Dir, St, []).
289
290add_materials(Ms, Dir, St) ->
291    add_materials_1(Ms, Dir, St, []).
292
293add_materials_1([{Name,Mat0}|Ms], Dir, St0, NewNames) ->
294    Mat1 = add_defaults(Mat0),
295    Maps = load_maps(prop_get(maps, Mat1, []), Dir),
296    Mat = keyreplace(maps, 1, Mat1, {maps,Maps}),
297    case add(Name, Mat, St0) of
298	#st{}=St ->
299	    add_materials_1(Ms, Dir, St, NewNames);
300	{#st{}=St,NewName} ->
301	    add_materials_1(Ms, Dir, St, [{Name,NewName}|NewNames])
302    end;
303add_materials_1([], _, St, NewNames) -> {St,NewNames}.
304
305add_defaults([]) ->
306    add_defaults([{opengl,[]},{maps,[]}]);
307add_defaults(Props0) ->
308    OpenGL0 = prop_get(opengl, Props0, []),
309    OpenGL = add_defaults_1(OpenGL0),
310    Props = [{opengl,OpenGL}|lists:keydelete(opengl, 1, Props0)],
311    case prop_get(maps, Props) of
312	undefined -> [{maps,[]}|Props];
313	_ -> Props
314    end.
315
316add_defaults_1(P) ->
317    Def = {1.0,1.0,1.0,1.0},
318    VertexColor = valid_vertex_color(prop_get(vertex_colors, P, ignore)),
319    Diff = norm(prop_get(diffuse, P, Def)),
320    Emission = norm(prop_get(emission, P, {0.0,0.0,0.0,1.0})),
321    [{diffuse, Diff},
322     {emission,Emission},
323     {metallic, def_metallic(P, Diff)},
324     {roughness, def_roughness(P)},
325     {vertex_colors,VertexColor}].
326
327def_metallic(P, Diff) ->
328    case prop_get(metallic, P) of
329        undefined ->
330            case prop_get(specular, P) of
331                undefined -> ?DEF_METALLIC;
332                Spec -> specular_to_metal(norm(Spec), Diff)
333            end;
334        Def when is_float(Def) -> Def
335    end.
336
337def_roughness(P) ->
338    case prop_get(roughness, P) of
339        undefined ->
340            case prop_get(shininess, P) of
341                undefined -> ?DEF_ROUGHNESS;
342                Shin -> 1.0 - min(1.0, Shin)
343            end;
344        Def when is_float(Def) -> Def
345    end.
346
347%% For future compatibility, ignore anything that we don't recognize.
348valid_vertex_color(multiply) -> set;
349valid_vertex_color(set) -> set;
350valid_vertex_color(_) -> ignore.
351
352norm({_,_,_,_}=Color) -> Color;
353norm({R,G,B}) -> {R,G,B,1.0}.
354
355update_materials([{Name,Mat0}|Ms], St) ->
356    Mat1 = add_defaults(Mat0),
357    Dir  = wings_pref:get_value(current_directory),
358    Maps = load_maps(prop_get(maps, Mat1, []), Dir),
359    Mat = keyreplace(maps, 1, Mat1, {maps,Maps}),
360    update_materials(Ms, update(Name, Mat, St));
361update_materials([], St) -> St.
362
363load_maps([{Key,Filename}|T], Dir) when is_list(Filename) ->
364    case load_map(Filename, Dir) of
365	none -> load_maps(T, Dir);
366	Map -> [{Key,Map}|load_maps(T, Dir)]
367    end;
368load_maps([{Key,{W,H,Bits}}|T], Dir) ->
369    E3dImage = #e3d_image{type=r8g8b8,order=lower_left,
370			  width=W,height=H,image=Bits},
371    Id = wings_image:new(atom_to_list(Key), E3dImage),
372    [{Key,Id}|load_maps(T, Dir)];
373load_maps([{Key,#e3d_image{name=Name0}=E3dImage}|T], Dir) ->
374    Name = case Name0 of
375	       [] -> atom_to_list(Key);
376	       _ when is_list(Name0) -> Name0;
377	       _ -> atom_to_list(Key)
378	   end,
379    Id = wings_image:new(Name, E3dImage),
380    [{Key,Id}|load_maps(T, Dir)];
381load_maps([{_,none}|T], Dir) ->
382    load_maps(T, Dir);
383load_maps([{_,Id}=Map|T], Dir) when is_integer(Id) ->
384    [Map|load_maps(T, Dir)];
385load_maps([], _) -> [].
386
387load_map(MapName, Dir) ->
388    try load_map_0(MapName, Dir) of
389	none -> none;
390	Im when is_integer(Im) -> Im
391    catch
392	error:R:ST ->
393	    io:format("~p\n", [R]),
394	    io:format("~P\n", [ST,20]),
395	    none
396    end.
397
398load_map_0(File, Dir) ->
399    case wings_image:find_image(Dir, File) of
400        false -> load_map_1(File, Dir);
401        {true, Id}  -> Id
402    end.
403
404load_map_1(File0, Dir) ->
405    File = filename:absname(File0, Dir),
406    Ps = [{filename,File},{order,lower_left},{alignment,1}],
407    case wings_image:image_read(Ps) of
408	#e3d_image{}=Im ->
409	    Name = filename:rootname(filename:basename(File)),
410	    wings_image:new(Name, Im);
411	{error,Error} ->
412            case file:format_error(Error) of
413                "unknown" ++ _ ->
414                    io:format(?__(1,"Failed to load") ++ " \"~ts\": ~p\n",
415                              [File,Error]);
416                ErrStr ->
417                    io:format(?__(1,"Failed to load") ++ " \"~ts\": ~s\n",
418                              [File,ErrStr])
419            end,
420	    none
421    end.
422
423add(default, _, #st{}=St) -> St;
424add(Name, Mat0, #st{mat=MatTab}=St) ->
425    Mat = sort([{K,sort(L)} || {K,L} <- Mat0]),
426    case gb_trees:lookup(Name, MatTab) of
427	none ->
428	    St#st{mat=gb_trees:insert(Name, Mat, MatTab)};
429	{value,Mat} -> St;
430	{value,_} ->
431	    NewName = new_name(atom_to_list(Name), MatTab),
432	    {add(NewName, Mat, St),NewName}
433    end.
434
435update(Name, Mat0, #st{mat=MatTab}=St) ->
436    Mat = sort([{K,sort(L)} || {K,L} <- Mat0]),
437    St#st{mat=gb_trees:update(Name, Mat, MatTab)}.
438
439new_name(Name0, Tab) ->
440    Names = [atom_to_list(N) || N <- gb_trees:keys(Tab)],
441    Name = wings_util:unique_name(Name0, Names),
442    list_to_atom(Name).
443
444has_texture(Name, Mtab) ->
445    Mat = gb_trees:get(Name, Mtab),
446    has_texture(Mat).
447
448has_texture(Mat) ->
449    Maps = prop_get(maps, Mat, []),
450    Maps =/= [].
451
452apply_material(Name, Mtab, false, RS0) ->
453    case wings_shaders:set_state(material, Name, RS0) of
454        {false, RS0} ->
455            fun() -> RS0 end;
456        {true, RS1} ->
457            apply_material_1(Name, Mtab, false, RS1)
458    end;
459apply_material(Name, Mtab, true, RS) ->
460    %% io:format("apply ~p~n",[Name]),
461    apply_material_1(Name, Mtab, true, wings_shaders:clear_state(material,RS)).
462
463apply_material_1(Name, Mtab, ActiveVertexColors, RS0) when is_atom(Name) ->
464    case maps:get({material, Name}, RS0, undefined) of
465        undefined ->
466            Props = material_prop(Name, Mtab),
467            apply_material_2(Props, ActiveVertexColors, RS0#{{material, Name}=>Props});
468        Props ->
469            apply_material_2(Props, ActiveVertexColors, RS0)
470    end.
471
472apply_material_2(Props, true, RS0) ->
473    case lists:keysearch(vertex_colors, 1, Props) of
474        {value, {_, ignore}} ->
475            gl:disableClientState(?GL_COLOR_ARRAY),
476            RS = lists:foldl(fun apply_material_3/2, RS0, Props),
477            fun() -> gl:enableClientState(?GL_COLOR_ARRAY), RS end;
478        _ ->
479            RS = lists:foldl(fun apply_material_3/2, RS0, Props),
480            fun() -> RS end
481    end;
482apply_material_2(Props, _, RS0) ->
483    RS = lists:foldl(fun apply_material_3/2, RS0, Props),
484    fun() -> RS end.
485
486apply_material_3({{tex, Type}=TexType, TexId}, Rs0) ->
487    case wings_shaders:set_state(TexType, TexId, Rs0) of
488        {false, Rs0} ->
489            Rs0;
490        {true, Rs1} when TexId =:= none ->
491            wings_shaders:set_uloc(texture_var(Type), enable(false), Rs1);
492        {true, Rs1} ->
493            gl:activeTexture(?GL_TEXTURE0 + tex_unit(Type)),
494            gl:bindTexture(?GL_TEXTURE_2D, TexId),
495            gl:activeTexture(?GL_TEXTURE0),
496            wings_shaders:set_uloc(texture_var(Type), enable(true), Rs1)
497    end;
498apply_material_3({Type, Value}, Rs0)
499  when Type =:= diffuse; Type =:= emission; Type =:= metallic; Type =:= roughness ->
500    wings_shaders:set_uloc(Type, Value, Rs0);
501apply_material_3({_Type,_}, Rs0) ->
502    %% io:format("~p:~p: unsupported type ~p~n",[?MODULE,?LINE,_Type]),
503    Rs0.
504
505specular_to_metal(Props) ->
506    S = prop_get(specular,Props),
507    D = prop_get(diffuse, Props),
508    specular_to_metal(S, D).
509
510specular_to_metal({SR,SG,SB,_},{DR,DG,DB,_}) ->
511    S0 = {SR,SG,SB},
512    D0 = {DR,DG,DB},
513    Len = e3d_vec:len(S0),
514    S1 = e3d_vec:divide(S0, max(1.0, Len)),
515    ACos = min(1.0, e3d_vec:dot(e3d_vec:norm(D0), S1)),
516    Linear = 1.0 - math:acos(ACos) * 2 / math:pi(),
517    %% io:format("~p ~n", [Linear]),
518    Linear.
519
520specular_from_metal(GL) ->
521    specular_from_metal(prop_get(metallic, GL, ?DEF_METALLIC), prop_get(diffuse, GL)).
522
523specular_from_metal(Met, {R,G,B,_A}) ->
524    norm(wings_color:mix(Met, {R,G,B}, {0.1,0.1,0.1})).
525
526add_old_props(Mat) ->
527    GL = prop_get(opengl, Mat),
528    Added = case prop_get(specular, GL) of
529                undefined -> %% Assume old props is missing
530                    Spec = specular_from_metal(prop_get(metallic, GL, ?DEF_METALLIC),
531                                               prop_get(diffuse, GL)),
532                    Rough = prop_get(roughness, GL, ?DEF_ROUGHNESS),
533                    [{ambient, {0.0,0.0,0.0,0.0}}, {specular, Spec},
534                     {shininess, 1.0 - Rough} | GL];
535                _ -> GL
536            end,
537    [{opengl, Added}|lists:keydelete(opengl, 1, Mat)].
538
539material_prop(Name, Mtab) ->
540    Mat = gb_trees:get(Name, Mtab),
541    OpenGL = prop_get(opengl, Mat),
542    Maps = prop_get(maps, Mat, []),
543    case wings_pref:get_value(show_textures) of
544        true ->
545            [{{tex,diffuse}, get_texture_map(diffuse, Maps)},
546             {{tex,normal}, get_normal_map(Maps)},
547             {{tex,pbr_orm},  get_pbr_map(Maps)},
548             {{tex,emission}, get_texture_map(emission, Maps)}
549             |OpenGL];
550        false ->
551            [{{tex,diffuse}, none},
552             {{tex,normal}, get_normal_map(Maps)},
553             {{tex,pbr_orm},  none},
554             {{tex,emission}, none}
555             |OpenGL]
556    end.
557
558get_texture_map(Type, Maps) ->
559    image_id(Type, prop_get(Type, Maps, none)).
560
561get_pbr_map(Maps) ->
562    PBRId = [prop_get(occlusion, Maps, none),
563             prop_get(roughness, Maps, none),
564             prop_get(metallic, Maps, none)],
565    image_id(combined, PBRId).
566
567get_normal_map(Maps) ->
568    case wings_pref:get_value(show_normal_maps, true) of
569        false -> none;
570        true ->
571            case prop_get(normal, Maps, none) of
572                none -> image_id(normal, prop_get(bump, Maps, none));
573                Map -> image_id(normal, Map)
574            end
575    end.
576
577image_id(_, none) -> none;
578image_id(_, [none,none,none]) -> none;
579image_id(normal, Map) -> wings_image:bumpid(Map);
580image_id(combined, Map) -> wings_image:combid(Map);
581image_id(_, Map) -> wings_image:txid(Map).
582
583enable(true)  -> 1;
584enable(false) -> 0.
585
586texture_var(diffuse) -> 'UseDiffuseMap';
587texture_var(normal) ->  'UseNormalMap';
588texture_var(pbr_orm) -> 'UsePBRMap'; %% red = occlusion green = roughness blue = metallic
589texture_var(emission) -> 'UseEmissionMap'.
590
591tex_unit(diffuse) -> ?DIFFUSE_MAP_UNIT;
592tex_unit(normal) -> ?NORMAL_MAP_UNIT;
593tex_unit(pbr_orm) -> ?PBR_MAP_UNIT; %% red = occlusion green = roughness blue = metallic
594tex_unit(emission) -> ?EMISSION_MAP_UNIT.
595
596%% Return the materials used by the objects in the scene.
597
598used_materials(#st{mat=Mat0}=St) ->
599    MF = fun(_, We) ->
600                 wings_facemat:used_materials(We)
601         end,
602    RF = fun(M, A) -> ordsets:union(M, A) end,
603    Used0 = wings_obj:dfold(MF, RF, ordsets:new(), St),
604    Used1 = sofs:from_external(Used0, [name]),
605    Mat = sofs:relation(gb_trees:to_list(Mat0), [{name,data}]),
606    Used = sofs:restriction(Mat, Used1),
607    [{Name,add_old_props(M)} || {Name,M} <- sofs:to_external(Used)].
608
609%% Return all image ids used by materials.
610
611used_images(#st{mat=Mat}) ->
612    used_images_1(gb_trees:values(Mat), []).
613
614used_images_1([M|Ms], Acc0) ->
615    Maps = prop_get(maps, M, []),
616    Acc = [Id || {_,Id} <- Maps, is_integer(Id)] ++ Acc0,
617    used_images_1(Ms, Acc);
618used_images_1([], Acc) -> gb_sets:from_list(Acc).
619
620is_transparent(Name, Mtab) ->
621    Mat = gb_trees:get(Name, Mtab),
622    is_mat_transparent(Mat).
623
624is_mat_transparent(Mat) ->
625    OpenGL = proplists:get_value(opengl, Mat, []),
626    Trans = lists:any(fun({diffuse,{_,_,_,Alpha}}) when Alpha < 1.0 -> true;
627                         (_) -> false
628                      end, OpenGL),
629    case Trans orelse proplists:get_value(maps, Mat, false) of
630        true  -> true;
631        false -> false;
632        Maps ->
633            case proplists:get_value(diffuse,Maps,undefined) of
634                undefined -> false ;
635                DiffMap ->
636                    #e3d_image{bytes_pp=Bpp} = wings_image:info(DiffMap),
637                    Bpp == 4
638            end
639    end.
640
641%% needed_attributes(We, St) -> [Attr]
642%%     Attr = color|uv|tangent
643%%  Return a ordered list of the type of attributes that are needed
644%%  according to the materials.
645%%  tangent requires uv since it needs the uv's to calculate tangent space
646needed_attributes(We, #st{mat=Mat}) ->
647    Used = wings_facemat:used_materials(We),
648    needed_attributes_1(Used, Mat, false, false, false).
649
650needed_attributes_1(_, _, true, _, true) -> [color,uv,tangent];
651needed_attributes_1([M|Ms], MatTab, Col0, UV0, TV0) ->
652    Mat = gb_trees:get(M, MatTab),
653    TV = TV0 orelse needs_tangents(Mat),
654    UV = UV0 orelse needs_uvs(Mat),
655    Col = Col0 orelse needs_vertex_colors(Mat),
656    needed_attributes_1(Ms, MatTab, Col, UV, TV);
657needed_attributes_1([], _, Col, UV, TV) ->
658    L = if TV -> [uv, tangent];
659	   UV -> [uv];
660	   true -> []
661	end,
662    case Col of
663	true -> [color|L];
664	false -> L
665    end.
666
667needs_vertex_colors(Mat) ->
668    OpenGL = prop_get(opengl, Mat),
669    prop_get(vertex_colors, OpenGL, ignore) =/= ignore.
670
671needs_uvs(Mat) ->
672    has_texture(Mat).
673
674needs_tangents(Mat) ->
675    Maps = prop_get(maps, Mat, []),
676    none =/= get_normal_map(Maps).
677
678-define(PREVIEW_SIZE, 150).
679
680edit(Name, Assign, #st{mat=Mtab}=St) ->
681    Mat = gb_trees:get(Name, Mtab),
682    DrawSphere = setup_sphere(),
683    {dialog,Qs,Fun} = edit_dialog(Name, Assign, St, Mat, DrawSphere),
684    Res = wings_dialog:dialog(?__(1,"Material Properties: ")++atom_to_list(Name),
685                              Qs, Fun),
686    wings_vbo:delete(DrawSphere),
687    Res.
688
689edit_dialog(Name, Assign, St=#st{mat=Mtab0}, Mat0, DrawSphere) ->
690    OpenGL0 = prop_get(opengl, Mat0),
691    VertexColors0 = prop_get(vertex_colors, OpenGL0, ignore),
692    {Diff0,Opacity0} = ask_prop_get(diffuse, OpenGL0),
693    Met0 = prop_get(metallic, OpenGL0),
694    Roug0 = prop_get(roughness, OpenGL0),
695    {Emiss0,_} = ask_prop_get(emission, OpenGL0),
696
697    Maps = prop_get(maps,Mat0),
698    MapList = [{{tex,diffuse},   get_texture_map(diffuse, Maps)},
699               {{tex,normal},    get_normal_map(Maps)},  %% Have no tangents
700               {{tex,pbr_orm},   get_pbr_map(Maps)},
701               {{tex,emission},  get_texture_map(emission, Maps)}],
702
703    Preview = fun(GLCanvas, Fields) ->
704                      wings_light:init_opengl(),
705                      mat_preview(GLCanvas,Fields,DrawSphere,MapList)
706	      end,
707    Refresh = fun(_Key, _Value, Fields) ->
708		      GLCanvas = wings_dialog:get_widget(preview, Fields),
709                      case wxWindow:isShown(GLCanvas) andalso os:type() of
710                          false -> ok;
711                          {_, darwin} -> %% workaround wxWidgets 3.0.4 and mojave
712                              wings_gl:setCurrent(GLCanvas, ?GET(gl_context)),
713                              Preview(GLCanvas, Fields),
714                              wxGLCanvas:swapBuffers(GLCanvas);
715                          _ ->
716                              wxWindow:refresh(GLCanvas)
717                      end
718	      end,
719    VtxColMenu = vertex_color_menu(VertexColors0),
720    OptDef = [{hook, Refresh}, {proportion,1}],
721    TexOpt = [{range,{0.0,1.0}}, {digits, 6}|OptDef],
722
723    Qs1 = {hframe,
724           [{custom_gl,?PREVIEW_SIZE,?PREVIEW_SIZE,Preview,
725             [{key, preview}, {proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}]},
726            {label_column,
727             [{?__(1,"Base Color"),{slider,{color,Diff0, [{key,diffuse}|OptDef]}}},
728              {?__(7,"Metallic"),  {slider,{text, Met0,  [{key,metallic}|TexOpt]}}},
729              {?__(8,"Roughness"), {slider,{text, Roug0, [{key,roughness}|TexOpt]}}},
730              {?__(4,"Emission"),  {slider,{color,Emiss0,[{key,emission}|OptDef]}}},
731              separator,
732              {?__(6,"Opacity"), {slider,{text,Opacity0, [{key,opacity}|TexOpt]}}},
733              {"Vertex Colors", VtxColMenu}
734             ], [{proportion,2}]},
735            {value, Mat0, [{key,material}]}], [{proportion, 1},{title," Wings 3D "}]},
736    %% Check for plugin's material editor available and inset their information
737    %% in a dropdown list to allow users to choose their properties via a new dialog
738    Qs2 = plugin_dlg_menu(Name, wings_plugin:has_dialog(material_editor_setup)),
739    Qs = {vframe_dialog, [Qs1 | Qs2], [{buttons, [ok, cancel]}, {key, result}]},
740    Ask = fun([{diffuse,Diff},
741               {metallic, Met},
742               {roughness, Roug},
743               {emission,Emiss},
744               {opacity,Opacity},
745               {vertex_colors,VertexColors},
746               {material,Mat1}|More]) ->
747              %% storing the latest Render engine as the preferred one
748              case lists:keyfind(plugin,1,More) of
749                  {plugin,{none,none}} -> wings_pref:delete_value(material_default_plugin);
750                  {plugin,Value} -> wings_pref:set_value(material_default_plugin,Value)
751              end,
752              OpenGL = [ask_prop_put(diffuse, Diff, Opacity),
753                        {metallic, Met},
754                        {roughness, Roug},
755                        ask_prop_put(emission, Emiss, Opacity),
756                        {vertex_colors,VertexColors}],
757              %% Updating Wings3D's material properties only.
758              %% The plugin's material properties were set in its own dialog event.
759              Mat = keyreplace(opengl, 1, Mat1, {opengl,OpenGL}),
760              Mtab = gb_trees:update(Name, Mat, Mtab0),
761              maybe_assign(Assign, Name, St#st{mat=Mtab})
762          end,
763    {dialog,Qs,Ask}.
764
765plugin_dlg_hook(Name) ->
766    {hook,
767        fun(Key, Value, Store) ->
768            case Key of
769                plugin ->
770                    {Mod,_} = Value,
771                    wings_dialog:enable(show_plugin_dlg,Mod=/=none,Store);
772                show_plugin_dlg ->
773                    {PlgMod,_} = wings_dialog:get_value(plugin,Store),
774                    Mat0 = wings_dialog:get_value(material,Store),
775                    Env = wx:get_env(),
776                    spawn(fun() ->
777                        %% Need open dialog in dialog from another process
778                        wx:set_env(Env),
779                        SetValue =
780                            fun(Res) ->
781                                {ok,Mat} =  plugin_results(Name,Mat0,Res,PlgMod),
782                                wings_dialog:set_value(material, Mat, Store),
783                                {return, Mat}
784                            end,
785                        [{PlgName,Qs}] = PlgMod:dialog({material_editor_setup,Name,Mat0}, []),
786                        Title = PlgName ++ ": " ++ atom_to_list(Name),
787                        wings_dialog:dialog(Title,[{vframe, [Qs]}],SetValue),
788                        wings_wm:psend(send_once, dialog_blanket, show)
789                    end)
790            end
791        end}.
792
793plugin_dlg_menu(_, []) -> [];
794plugin_dlg_menu(Name, Plugins) ->
795    [{_,PlgId}|_] = Opts = [{?__(1,"Select..."),{none,none}}]++[{PlgName,{Pm,Tag}} || {Pm,{PlgName,Tag}} <- lists:sort(Plugins)],
796    DefPlg = wings_pref:get_value(material_default_plugin,PlgId),
797    Hook = plugin_dlg_hook(Name),
798    [{hframe, [
799         {label_column,
800          [{?__(2,"Available"), {menu, Opts, DefPlg, [{key,plugin},Hook]}}]},
801         {button, ?__(3,"Edit Property..."),show_plugin_dlg,[{key,show_plugin_dlg},Hook]}
802     ],[{title," " ++ ?__(4,"Plugins") ++ " "}]}
803    ].
804
805vertex_color_menu(multiply) ->
806    vertex_color_menu(set);
807vertex_color_menu(Def) ->
808    {menu,[{"Ignore",ignore,[{info,"Ignore vertex colors"}]},
809	   {"Set",set,[{info,"Show vertex colors"}]}],Def,
810     [{info,"Choose how to use vertex colors"},
811      {key,vertex_colors}]}.
812
813maybe_assign(false, _, St) -> St;
814maybe_assign(true, Name, St) -> set_material(Name, St).
815
816plugin_results(Name, Mat0, Res0, Mod) ->
817    case wings_plugin:dialog_result({material_editor_result,Name,Mat0}, Res0, Mod) of
818        {Mat,[]} -> {ok,Mat};
819        {_,Res} ->
820            io:format(?__(1,"Material editor plugin(s) left garbage:~n    ~P~n"),
821                      [Res,20]),
822            wings_u:error_msg(?__(2,"Plugin(s) left garbage"))
823    end.
824
825ask_prop_get(Key, Props) ->
826    {R,G,B,Alpha} = prop_get(Key, Props),
827    {{R,G,B},Alpha}.
828
829ask_prop_put(Key, {R,G,B}, Opacity) ->
830    {Key,{R,G,B,Opacity}}.
831
832setup_sphere() ->
833    #{size:=Len, tris:=Tris, ns:=Normals, uvs:=UVs, tgs:=Tgs} =
834        wings_shapes:tri_sphere(#{subd=>4, ccw=>false, normals=>true, tgs=>true,
835                                  uvs=>true, scale=>0.45}),
836    Data = zip(Tris, Normals, UVs, Tgs),
837    Layout = [vertex, normal, uv, tangent],
838    D = fun(#{preview := PreviewMat} = RS0) ->
839                RS1 = wings_shaders:use_prog(1, RS0),
840                RS2 = lists:foldl(fun apply_material_3/2, RS1, PreviewMat),
841                gl:drawArrays(?GL_TRIANGLES, 0, Len*3),
842                wings_shaders:use_prog(0, RS2)
843        end,
844    wings_vbo:new(D, Data, Layout).
845
846mat_preview(Canvas, Common, Vbo, Maps) ->
847    {W,H} = wxWindow:getSize(Canvas),
848    Scale = wxWindow:getContentScaleFactor(Canvas),
849    gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
850    gl:viewport(0, 0, round(W*Scale), round(H*Scale)),
851    {BR,BG,BB, _} = wxWindow:getBackgroundColour(wxWindow:getParent(Canvas)),
852    %% wxSystemSettings:getColour(?wxSYS_COLOUR_BACKGROUND),
853    BGC = fun(Col) -> (Col-15) / 255 end,
854    gl:clearColor(BGC(BR),BGC(BG),BGC(BB),1.0),
855    gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT),
856    gl:matrixMode(?GL_PROJECTION),
857    gl:loadIdentity(),
858    Fov = 45.0, Aspect = W/H,
859    MatP = e3d_transform:perspective(Fov, Aspect, 0.01, 256.0),
860    gl:multMatrixd(e3d_transform:matrix(MatP)),
861    gl:matrixMode(?GL_MODELVIEW),
862    gl:loadIdentity(),
863    Dist = (0.5/min(1.0,Aspect)) / math:tan(Fov/2*math:pi()/180),
864    Eye = {0.0,0.0,Dist}, Up = {0.0,1.0,0.0},
865    MatMV = e3d_transform:lookat(Eye, {0.0,0.0,0.0}, Up),
866    gl:multMatrixd(e3d_transform:matrix(MatMV)),
867    gl:shadeModel(?GL_SMOOTH),
868    Alpha = wings_dialog:get_value(opacity, Common),
869    Diff  = preview_mat(diffuse, Common, Alpha),
870    Emis  = preview_mat(emission, Common, Alpha),
871    Metal = {metallic, wings_dialog:get_value(metallic, Common)},
872    Rough = {roughness, wings_dialog:get_value(roughness, Common)},
873    Material = [Diff, Emis, Metal, Rough | Maps],
874    gl:enable(?GL_BLEND),
875    gl:enable(?GL_DEPTH_TEST),
876    gl:enable(?GL_CULL_FACE),
877    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
878    gl:color4ub(255, 255, 255, 255),
879    RS = #{ws_eyepoint=>Eye, view_from_world=> MatMV, preview=>Material},
880    wings_dl:call(Vbo, RS),
881    gl:disable(?GL_BLEND),
882    gl:shadeModel(?GL_FLAT),
883    gl:popAttrib(),
884    case os:type() of
885        {_, darwin} ->
886            %% Known problem during redraws before window is shown
887            %% only reset error check
888            _ = gl:getError();
889        _ ->
890            wings_develop:gl_error_check("Rendering mat viewer")
891    end.
892
893zip(Vs, Ns, UVs, Tgs) ->
894    zip_0(Vs, Ns, UVs, Tgs, []).
895
896zip_0([V|Vs], [N|Ns], [UV|UVs], [T|Ts], Acc) ->
897    zip_0(Vs, Ns, UVs,Ts, [V,N,UV,T|Acc]);
898zip_0([], [], [], [],Acc) -> Acc.
899
900preview_mat(Key, Colors, Alpha) ->
901    {R,G,B} = wings_dialog:get_value(Key, Colors),
902    {Key, {R,G,B,Alpha}}.
903
904%%% Return color in texture for the given UV coordinates.
905
906color(Face, UV, We, #st{mat=Mtab}) ->
907    Name = wings_facemat:face(Face, We),
908    Props = gb_trees:get(Name, Mtab),
909    Maps = prop_get(maps, Props),
910    case prop_get(diffuse, Maps, none) of
911	none ->
912	    OpenGL = prop_get(opengl, Props),
913	    {R,G,B,_} = prop_get(diffuse, OpenGL),
914	    wings_color:share({R,G,B});
915	DiffMap ->
916	    color_1(UV, wings_image:info(DiffMap))
917    end;
918color(_Face, {_,_,_}=RGB, _We, _St) -> RGB.
919
920color_1(_, none) -> wings_color:white();
921color_1(none, _) -> wings_color:white();
922color_1({U0,V0}, #e3d_image{width=W,height=H,image=Bits}) ->
923    U = (((round(U0*W) rem W) + W) rem W),
924    V = ((round(V0*H) rem H) + H) rem H,
925    Pos = V*W*3 + U*3,
926    <<_:Pos/binary,R:8,G:8,B:8,_/binary>> = Bits,
927    wings_util:share(R/255, G/255, B/255).
928
929prop_get(Key, Props) ->
930    proplists:get_value(Key, Props).
931
932prop_get(Key, Props, Def) ->
933    proplists:get_value(Key, Props, Def).
934