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