Skip to content

Commit

Permalink
Move hard-edge from vertex-normal calcs from e3d to wings
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.
  • Loading branch information
dgud committed Oct 15, 2024
1 parent 5afc4fe commit 68e587c
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 68e587c

Please sign in to comment.