Skip to content

Commit

Permalink
Move hard-edge from vertex-normal calcs from e3d to wings (#588)
Browse files Browse the repository at this point in the history
To be able to improve finding hardedges from vertex normals, we
need to postpone the calculation til we have a #we{} to work on,
so we can fold over faces and/or vertices.

So include the normal information in wings_we_build.
If there are no hardedges and and vertex normals try to find an
approximation of the hardedges from the vertex normals.

I do that in 2 steps first add edges that are clearly hard
or delete cleary not hard.

Pick out vertices which are neither.

Second pass loop around vertices and find hard edges.

NOTE:  Improved hard-edges from vertex normals calculation when importing models.
  • Loading branch information
dgud authored Oct 15, 2024
1 parent 5afc4fe commit 8b62777
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 53 deletions.
3 changes: 1 addition & 2 deletions src/wings_import.erl
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ import_object(#e3d_object{name=_Name,obj=Mesh0}) ->
%%io:format("\n~s:\n", [_Name]),
Mesh1 = e3d_mesh:merge_vertices(Mesh0),
Mesh2 = e3d_mesh:clean_faces(Mesh1),
Mesh3 = e3d_mesh:transform(Mesh2),
Mesh = e3d_mesh:hard_edges_from_normals(Mesh3),
Mesh = e3d_mesh:transform(Mesh2),
import_mesh(material, Mesh).

-define(P(N), {N,fun N/2}).
Expand Down
31 changes: 16 additions & 15 deletions src/wings_we.erl
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@

%% build() -> We'
%% Create a we from faces and vertices or a mesh.
build(Mode, #e3d_mesh{fs=Fs0,vs=Vs,vc=Vc0,tx=Tx,he=He}) when is_atom(Mode) ->
build(Mode, #e3d_mesh{fs=Fs0,vs=Vs,vc=Vc0,tx=Tx,he=He, ns=NTab}) when is_atom(Mode) ->
Vc = supress_alpha(Vc0),
Fs = translate_faces(Fs0, list_to_tuple(Vc), list_to_tuple(Tx), []),
Fs = translate_faces(Fs0, list_to_tuple(Vc), list_to_tuple(Tx), list_to_tuple(NTab), []),
wings_we_build:we(Fs, Vs, He);
build(Fs, Vs) ->
wings_we_build:we(Fs, Vs, []).
Expand Down Expand Up @@ -542,23 +542,24 @@ show_faces_1(Faces, #we{es=Etab0}=We0) ->
%%% Build Winged-Edges.
%%%

translate_faces([#e3d_face{vs=Vs,vc=Vc0,tx=Tx0,mat=Mat0}|Fs], Vcs, Txs, Acc) ->
translate_faces([#e3d_face{vs=Vs,vc=Vc0,tx=Tx0,ns=Ns0,mat=Mat0}|Fs], Vcs, Txs, NTab, Acc) ->
Mat = translate_mat(Mat0),
FaceData = case {Tx0,Vc0} of
{[],[]} -> {Mat,Vs};
{Tx1,Vc1} ->
Tx = if Tx1 =/= [] ->
[element(Tx+1, Txs) || Tx <- Tx1];
true -> none
FaceData = case {Tx0,Vc0,Ns0} of
{[],[],[]} -> {Mat,Vs};
{Tx1,Vc1,Ns1} ->
Tx = if Tx1 =/= [] -> [element(Tx+1, Txs) || Tx <- Tx1];
true -> none
end,
Vc = if Vc1 =/= [] ->
[element(Vc+1, Vcs) || Vc <- Vc1];
true -> none
Vc = if Vc1 =/= [] -> [element(Vc+1, Vcs) || Vc <- Vc1];
true -> none
end,
{Mat,Vs,Tx,Vc}
Ns = if Ns1 =/= [] -> [element(Ni+1, NTab) || Ni <- Ns1];
true -> none
end,
{Mat,Vs,Tx,Vc,Ns}
end,
translate_faces(Fs, Vcs, Txs, [FaceData|Acc]);
translate_faces([], _, _, Acc) -> reverse(Acc).
translate_faces(Fs, Vcs, Txs, NTab, [FaceData|Acc]);
translate_faces([], _, _, _, Acc) -> reverse(Acc).

translate_mat([]) -> default;
translate_mat([Mat]) -> Mat;
Expand Down
169 changes: 133 additions & 36 deletions src/wings_we_build.erl
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ build_and_fix_holes(_, _) ->
throw(bad_model).

build_rest(Es, Fs, Vs, HardEdges) ->
Htab = vpairs_to_edges(HardEdges, Es),
{Vct0,Etab,Ftab0,UvVcTab} = build_tables(Es),
{Vct0,Etab,Ftab0,UvVcTab,VNTab} = build_tables(Es),
Ftab = build_faces(Ftab0),
Vct = array:from_orddict(incident_tab(Vct0)),
Vpos = number_vertices(Vs, 0, []),
Expand All @@ -98,8 +97,13 @@ build_rest(Es, Fs, Vs, HardEdges) ->
%% that the greatest key is found in the edge table.
wings_util:array_greatest_key(Etab)+1
end,
We0 = #we{next_id=NextId,es=Etab,fs=Ftab,vc=Vct,vp=Vpos,he=Htab},
We = wings_va:set_edge_attrs(UvVcTab, We0),
HTab = vpairs_to_edges(HardEdges, Es),
We0 = #we{next_id=NextId,es=Etab,fs=Ftab,vc=Vct,vp=Vpos,he=HTab},
We1 = wings_va:set_edge_attrs(UvVcTab, We0),
We = case HardEdges =:= [] andalso VNTab =/= [] of
false -> We1;
true -> calc_hard_edges(VNTab, We1)
end,
assign_materials(Fs, We).

assign_materials([L|_], We) when is_list(L) -> We;
Expand Down Expand Up @@ -133,32 +137,27 @@ build_edges(Fs) ->
build_half_edges(Fs) ->
build_half_edges(Fs, 0, []).

build_half_edges([{_Material,Vs,none,Vc}|Fs], Face, Eacc0) -> % imported with vertex color only
build_half_edges_1(Vs, tx_filler(Vs), Vc, Fs, Face, Eacc0);
build_half_edges([{_Material,Vs,Tx,none}|Fs], Face, Eacc0) -> % imported with textures only
build_half_edges_1(Vs, Tx, tx_filler(Vs), Fs, Face, Eacc0);
build_half_edges([{_Material,Vs,Tx,Vc}|Fs], Face, Eacc0) -> % imported with textures and vertex color
build_half_edges_1(Vs, Tx, Vc, Fs, Face, Eacc0);
build_half_edges([{_Material,Vs,Tx,Vc,Ns}|Fs], Face, Eacc0) -> % imported with textures and vertex color
build_half_edges_1(Vs, Tx, Vc, Ns, Fs, Face, Eacc0);
build_half_edges([{_Material,Vs}|Fs], Face, Eacc0) -> % imported without textures or vertex color
build_half_edges_1(Vs, tx_filler(Vs), tx_filler(Vs), Fs, Face, Eacc0);
build_half_edges_1(Vs, none, none, none, Fs, Face, Eacc0);
build_half_edges([Vs|Fs], Face, Eacc0) -> % new primitives
build_half_edges_1(Vs, tx_filler(Vs), tx_filler(Vs), Fs, Face, Eacc0);
build_half_edges_1(Vs, none, none, none, Fs, Face, Eacc0);
build_half_edges([], _Face, HalfEdges) -> HalfEdges.

build_half_edges_1(Vs, UVs, VCs, Fs, Face, Acc0) ->
UvsVcs = zip(UVs,VCs),
Vuvs = zip(Vs, UvsVcs),
build_half_edges_1(Vs, UVs, VCs, Ns, Fs, Face, Acc0) ->
Vuvs = zip4(Vs, UVs, VCs, Ns),
Pairs = pairs(Vuvs),
Acc = build_face_edges(Pairs, Face, Acc0),
build_half_edges(Fs, Face+1, Acc).

build_face_edges([{Pred,_}|[{E0,{{_UVa,_VCa},{UVb,VCb}}},{Succ,_}|_]=Es], Face, Acc0) ->
build_face_edges([{Pred,_}|[{E0,{{_,_,_},{_UVb,_VCb,_Nb}=VtxInfo}},{Succ,_}|_]=Es], Face, Acc0) ->
Acc = case E0 of
{Vs,Ve}=Name when Vs < Ve ->
enter_half_edge(right, Name, Face, Pred, Succ, {UVb,VCb}, Acc0);
enter_half_edge(right, Name, Face, Pred, Succ, VtxInfo, Acc0);
{Vs,Ve} when Ve < Vs ->
Name = {Ve,Vs},
enter_half_edge(left, Name, Face, Pred, Succ, {UVb,VCb}, Acc0)
enter_half_edge(left, Name, Face, Pred, Succ, VtxInfo, Acc0)
end,
build_face_edges(Es, Face, Acc);
build_face_edges([_,_], _Face, Acc) -> Acc.
Expand All @@ -167,6 +166,14 @@ enter_half_edge(Side, Name, Face, Pred, Succ, UVVC,Tab0) ->
Rec = {Face,UVVC,edge_name(Pred),edge_name(Succ)},
[{Name,{Side,Rec}}|Tab0].

zip4([V|Vs], UVs0, VCs0, Ns0) ->
{Uv, UVs} = tx_filler(UVs0),
{Vc, VCs} = tx_filler(VCs0),
{N, Ns} = tx_filler(Ns0),
[{V, {Uv, Vc, N}} | zip4(Vs, UVs, VCs, Ns)];
zip4([], _, _, _) ->
[].

pairs(Vs) ->
pairs(Vs, Vs, []).

Expand All @@ -178,12 +185,8 @@ pairs([{V,T}], [{V1,T1},{V2,T2},{V3,T3}|_], Acc) ->
edge_name({Vs,Ve}=Name) when Vs < Ve -> Name;
edge_name({Vs,Ve}) -> {Ve,Vs}.

tx_filler(Vs) ->
tx_filler(Vs, none, []).

tx_filler([_|Vs], Col, Acc) ->
tx_filler(Vs, Col, [Col|Acc]);
tx_filler([], _Col, Acc) -> Acc.
tx_filler(none) -> {none, none};
tx_filler([H|TL]) -> {H,TL}.

combine_half_edges(HalfEdges) ->
combine_half_edges(HalfEdges, [], []).
Expand Down Expand Up @@ -213,12 +216,12 @@ vpairs_to_edges(HardNames0, Es) ->

build_tables(Edges) ->
Emap = make_edge_map(Edges),
build_tables(Edges, Emap, [], [], [], []).
build_tables(Edges, Emap, [], [], [], [], []).

build_tables([H|T], Emap, Vtab0, Etab0, Ftab0, UvVcTab0) ->
build_tables([H|T], Emap, Vtab0, Etab0, Ftab0, UvVcTab0, VNTab0) ->
{{Vs,Ve},{Edge,{Ldata,Rdata}}} = H,
{Lf,{LUV,LVC},Lpred,Lsucc} = Ldata,
{Rf,{RUV,RVC},Rpred,Rsucc} = Rdata,
{Lf,{LUV,LVC,LN},Lpred,Lsucc} = Ldata,
{Rf,{RUV,RVC,RN},Rpred,Rsucc} = Rdata,
Erec = #edge{vs=Vs,ve=Ve,lf=Lf,rf=Rf,
ltpr=edge_num(Lf, Lpred, Emap),
ltsu=edge_num(Lf, Lsucc, Emap),
Expand All @@ -228,13 +231,17 @@ build_tables([H|T], Emap, Vtab0, Etab0, Ftab0, UvVcTab0) ->
Ftab = [{Lf,Edge},{Rf,Edge}|Ftab0],
Vtab = [{Vs,Edge},{Ve,Edge}|Vtab0],
UvVcTab = case {LUV,RUV,LVC,RVC} of
{none,none,none,none} -> UvVcTab0;
{_,_,_,_} -> [{Edge,LUV,RUV,LVC,RVC}|UvVcTab0]
end,
build_tables(T, Emap, Vtab, Etab, Ftab, UvVcTab);
build_tables([], _Emap, Vtab, Etab0, Ftab, UvVcTab) ->
{none,none,none,none} -> UvVcTab0;
{_,_,_,_} -> [{Edge,LUV,RUV,LVC,RVC}|UvVcTab0]
end,
VNTab = case {LN,RN} of
{none, none} -> VNTab0;
{_,_} -> [{Edge,{LN,RN}}|VNTab0]
end,
build_tables(T, Emap, Vtab, Etab, Ftab, UvVcTab, VNTab);
build_tables([], _Emap, Vtab, Etab0, Ftab, UvVcTab, VNTab) ->
Etab = array:from_orddict(reverse(Etab0)),
{Vtab,Etab,Ftab,UvVcTab}.
{Vtab,Etab,Ftab,UvVcTab,VNTab}.

make_edge_map(Es) ->
make_edge_map(Es, []).
Expand Down Expand Up @@ -386,9 +393,9 @@ elim_vtx_map([], _, Acc) ->
elim_renum_vs([{Mat,Vs0}|Faces], [{Face,DupVs}|ToDo], Face, VtxMap, Acc) ->
Vs = elim_renum_vs_1(Vs0, DupVs, VtxMap),
elim_renum_vs(Faces, ToDo, Face+1, VtxMap, [{Mat,Vs}|Acc]);
elim_renum_vs([{Mat,Vs0,Tx,Vc}|Faces], [{Face,DupVs}|ToDo], Face, VtxMap, Acc) ->
elim_renum_vs([{Mat,Vs0,Tx,Vc,Ns}|Faces], [{Face,DupVs}|ToDo], Face, VtxMap, Acc) ->
Vs = elim_renum_vs_1(Vs0, DupVs, VtxMap),
elim_renum_vs(Faces, ToDo, Face+1, VtxMap, [{Mat,Vs,Tx,Vc}|Acc]);
elim_renum_vs(Faces, ToDo, Face+1, VtxMap, [{Mat,Vs,Tx,Vc,Ns}|Acc]);
elim_renum_vs([MatVs|Faces], ToDo, Face, VtxMap, Acc) ->
elim_renum_vs(Faces, ToDo, Face+1, VtxMap, [MatVs|Acc]);
elim_renum_vs([], [], _, _, Acc) -> reverse(Acc).
Expand Down Expand Up @@ -471,3 +478,93 @@ shared_digraph_1([{_,{{Lf,_,_,_},{Rf,_,_,_}}}|Es], G) ->
digraph:add_edge(G, Lf, Rf),
shared_digraph_1(Es, G);
shared_digraph_1([], _) -> ok.

calc_hard_edges(EdgeNsList, #we{fs=FTab} = We) ->
FaceNs0 = lists:foldl(fun(Face, Acc) ->
[{Face, wings_face:normal(Face, We)}|Acc]
end,
[], gb_trees:keys(FTab)),
FaceNs = lists:reverse(FaceNs0),
FaceVsNs = gb_trees:from_orddict(lists:sort(wings_we:normals(FaceNs, We, none))),

EdgeNs = array:from_orddict(lists:sort(EdgeNsList)),
{HE0,PossibleVs} = lists:foldl(fun({Face, FaceN}, Acc) ->
FaceVNs = gb_trees:get(Face, FaceVsNs),
find_hard_edges(Face, FaceN, FaceVNs, EdgeNs, Acc, We)
end, {[], []}, FaceNs),

HE1 = filter_edges(lists:usort(HE0), gb_trees:from_orddict(FaceNs), We#we.es),
HE2 = gb_sets:from_ordset(HE1),
HE3 = find_he_from_vertex(lists:usort(PossibleVs), EdgeNs, HE2, We),

We#we{he = HE3}.

find_hard_edges(Face, FlatNormal, SmoothNs0, EdgeNs, {He0, Possible0}, We) ->
Fun = fun(V, Edge, E, {[SN|SmoothNs], He, Possible}) ->
{VNS, VNE} = array:get(Edge, EdgeNs),
{VN, Other} = case E of
#edge{lf = Face, vs = V, ltsu = Next} -> {VNS, Next};
#edge{rf = Face, ve = V, rtsu = Next} -> {VNE, Next}
end,
try e3d_vec:dist_sqr(VN, FlatNormal) < ?EPSILON of
true ->
{SmoothNs, [Edge, Other|He], Possible};
false ->
%% Compare Normals
case e3d_vec:dist_sqr(VN,SN) < ?EPSILON of
true -> {SmoothNs, He, Possible};
false -> {SmoothNs, He, [V|Possible]}
end
catch error:function_clause ->
%% Missing vertex normal, assume face-normal or smooth?
%% Currently we make it smooth
{SmoothNs, He, Possible}
end
end,
{[], He, Poss} = wings_face:fold(Fun, {lists:reverse(SmoothNs0), He0, Possible0}, Face, We),
{He, Poss}.

%% Vertices with normals that neither point at face normal nor smooth vertex normal
%% They will have some edges that are hard (if exported from wings at least)
find_he_from_vertex([V|Vs], EdgeNs, HeAcc0, We) ->
F = fun(Edge, _Face, E, Acc) ->
{VNS, VNE} = array:get(Edge, EdgeNs),
case E of
#edge{vs = V, lf = F1, rf = F2} -> gb_trees:insert(F1, {VNS, Edge, F2}, Acc);
#edge{ve = V, rf = F1, lf = F2} -> gb_trees:insert(F1, {VNE, Edge, F2}, Acc)
end
end,
ED = wings_vertex:fold(F, gb_trees:empty(), V, We),
{_,Info} = gb_trees:smallest(ED),
HeAcc = lists:usort(pick_edges(Info, ED, [])),
find_he_from_vertex(Vs, EdgeNs, gb_sets:union(gb_sets:from_ordset(HeAcc),HeAcc0), We);
find_he_from_vertex([], _, HeAcc, _) ->
HeAcc.

pick_edges({N1, Edge, NextFace}, Tree0, Acc) ->
try gb_trees:take(NextFace, Tree0) of
{{N2, _, _} = Next, Tree} ->
try e3d_vec:dist_sqr(N1,N2) < ?EPSILON of
true -> pick_edges(Next, Tree, Acc);
false -> pick_edges(Next, Tree, [Edge|Acc])
catch error:function_clause ->
%% Vertex normals assume smooth normal
pick_edges(Next, Tree, Acc)
end
catch _E:_R ->
Acc
end.

%% Filter out edges between flat faces
filter_edges([Id|Es], FaceNs, ETab) ->
#edge{lf = LF, rf = RF} = array:get(Id, ETab),
case e3d_vec:dist_sqr(gb_trees:get(LF, FaceNs), gb_trees:get(RF, FaceNs)) of
Dist when Dist < ?EPSILON ->
filter_edges(Es, FaceNs, ETab);
_ ->
[Id|filter_edges(Es, FaceNs, ETab)]
end;
filter_edges([], _, _) ->
[].


0 comments on commit 8b62777

Please sign in to comment.