From 94e57d539bd535620b4e4e01586ca35be6b9e44c Mon Sep 17 00:00:00 2001 From: Edward Blake Date: Wed, 8 May 2024 22:50:37 -0400 Subject: [PATCH 1/2] x3d_import and wpc_wrl: Improvements * Normals implemented in importer. * creaseAngle implemented in importer when there are no normals. * Added X3D JSON in import/export NOTE: X3D/VRML Improvements, X3D JSON support. --- plugins_src/import_export/wpc_wrl.erl | 257 ++++++++--- plugins_src/import_export/x3d_import.erl | 541 +++++++++++++++++++---- 2 files changed, 633 insertions(+), 165 deletions(-) diff --git a/plugins_src/import_export/wpc_wrl.erl b/plugins_src/import_export/wpc_wrl.erl index ed495f5b6..429e34e1e 100644 --- a/plugins_src/import_export/wpc_wrl.erl +++ b/plugins_src/import_export/wpc_wrl.erl @@ -47,13 +47,15 @@ command({file,{export_selected,{wrl,Ask}}}, St) -> command(_, _) -> next. menu_entry(Menu) -> - Menu ++ [{"X3D/VRML (.x3d|.wrl)...", wrl, [option]}]. + Menu ++ [{"X3D/VRML (.x3d|.x3dj|.wrl)...", wrl, [option]}]. props(wrl) -> [{ext, ".wrl"},{ext_desc, ?__(1,"VRML 2.0 File")}]; props(x3d) -> - [{ext, ".x3d"},{ext_desc, ?__(2,"X3D File")}]. + [{ext, ".x3d"},{ext_desc, ?__(2,"X3D File")}]; +props(x3dj) -> + [{ext, ".x3dj"},{ext_desc, ?__(3,"X3D JSON File")}]. do_export(Ask, Op, _Exporter, _St) when is_atom(Ask) -> wpa:dialog(Ask, ?__(1,"VRML Export Options"), dialog(export), @@ -82,7 +84,8 @@ dialog(export) -> wpa:pref_set_default(?MODULE, default_filetype, ".png"), [{hframe,[{label,?__(2,"Output format") ++ ":"}, {menu, [ {?__(1,"VRML 2.0"), wrl}, - {?__(3,"X3D"), x3d} ], + {?__(3,"X3D"), x3d}, + {?__(4,"X3D JSON"), x3dj} ], wrl, [{key, exp_file_type}]}]}, wpa:dialog_template(?MODULE, tesselation), panel, @@ -93,8 +96,9 @@ dialog(export) -> %% faces and vertices for one material.. export(File_name, Export0, Attr) -> WhichExt = case filename:extension(File_name) of - ".x3d" -> x3d; - _ -> wrl + ".x3d" -> x3d; + ".x3dj" -> x3dj; + _ -> wrl end, %%io:format("~p~n~p~n",[Objs, Mat]), Filetype = proplists:get_value(default_filetype, Attr, ".png"), @@ -122,17 +126,40 @@ export(WhichExt, File_name, Export, ExportN, Dir) -> io:format(F, " \n", [BaseName_1]), io:format(F, " \n", [ExportedFrom]), io:format(F, " \n", []), - io:format(F, " \n", []) + io:format(F, " \n", []); + x3dj -> + BaseName = filename:basename(File_name), + ExportedFrom = io_lib:format("Exported from ~s", [Creator]), + BaseName_1 = unicode:characters_to_nfc_binary(BaseName), + io:format(F, "{ \"X3D\": {\n", []), + io:format(F, " \"encoding\":\"UTF-8\",\n", []), + io:format(F, " \"@profile\":\"Interchange\",\n", []), + io:format(F, " \"@version\":\"3.2\",\n", []), + io:format(F, " \"@xsd:noNamespaceSchemaLocation\":\"https://www.web3d.org/specifications/x3d-3.2.xsd\",\n", []), + io:format(F, " \"JSON schema\":\"https://www.web3d.org/specifications/x3d-4.0-JSONSchema.autogenerated.json\",\n", []), + io:format(F, " \"head\": {\n", []), + io:format(F, " \"meta\": [\n", []), + io:format(F, " { \"@name\":\"title\",\n", []), + io:format(F, " \"@content\":\"~s\" },\n", [BaseName_1]), + io:format(F, " { \"@name\":\"generator\",\n", []), + io:format(F, " \"@content\":\"~s\" } ] },\n", [ExportedFrom]), + io:format(F, " \"Scene\": {\n", []), + io:format(F, " \"-children\":[\n", []) end, + [#e3d_object{name=LastObjName}|_] = lists:reverse(Objs), + try foldl(fun(#e3d_object{name = Name, obj=Obj}, Used_mats0) -> + Name_1 = unicode:characters_to_nfc_binary(clean_id(Name)), case WhichExt of wrl -> - Name_1 = unicode:characters_to_nfc_binary(clean_id(Name)), io:format(F, "DEF ~s Transform {\n",[Name_1]), io:format(F, " children [\n",[]); x3d -> - Name_1 = unicode:characters_to_nfc_binary(clean_id(Name)), - io:format(F, " \n", [Name_1]) + io:format(F, " \n", [Name_1]); + x3dj -> + io:format(F, "{ \"Transform\": {\n",[]), + io:format(F, " \"@DEF\": \"~s\",\n",[Name_1]), + io:format(F, " \"-children\": [\n",[]) end, ObjMesh = e3d_mesh:vertex_normals(Obj), Meshes = e3d_mesh:split_by_material(ObjMesh), @@ -149,7 +176,10 @@ export(WhichExt, File_name, Export, ExportN, Dir) -> io:put_chars(F, "\n ]\n"), io:put_chars(F, "}\n\n"); x3d -> - io:put_chars(F, "\n \n") + io:put_chars(F, "\n \n"); + x3dj -> + io:put_chars(F, "\n ]\n"), + io:format(F, "} }~s\n\n", [cma(LastObjName =/= Name)]) end, Used_mats end, [], Objs) @@ -160,7 +190,10 @@ export(WhichExt, File_name, Export, ExportN, Dir) -> wrl -> ok; x3d -> io:put_chars(F, " \n"), - io:put_chars(F, "\n") + io:put_chars(F, "\n"); + x3dj -> + io:put_chars(F, " ] }\n"), + io:put_chars(F, "} }\n") end, ok = file:close(F). @@ -178,31 +211,31 @@ export_object(Output, #e3d_mesh{fs=Fs0,ns=NTab,vs=VTab,tx=UVTab,vc=ColTab}, Output(3, {start_tag, "geometry", "IndexedFaceSet"}), if ExportN == true -> - Output(4, {tag_attr, "normalPerVertex", "TRUE"}); + Output(4, {tag_attr_bool, "normalPerVertex", true, true}); true -> ignore end, if (ColTab == []) -> ignore; true -> %% Use vertex colors - Output(4, {tag_attr, "colorPerVertex", "TRUE"}) + Output(4, {tag_attr_bool, "colorPerVertex", true, true}) end, WriteCoordIndex = fun (IdxIndent) -> %% Write vertex indices - Output(IdxIndent, {tag_attr_array, "coordIndex", {fun(#e3d_face{vs=Vs}) -> print_face(Output, Vs) end,Fs}}) + Output(IdxIndent, {tag_attr_array, "coordIndex", {fun(#e3d_face{vs=Vs}) -> print_face(Output, Vs) end,Fs}, true}) end, WriteNormalIndex = fun (IdxIndent) -> if ExportN -> %% Write per-vertex normal index - Output(IdxIndent, {tag_attr_array, "normalIndex", {fun(#e3d_face{ns=Ns}) -> print_face(Output, Ns) end,Fs}}); + Output(IdxIndent, {tag_attr_array, "normalIndex", {fun(#e3d_face{ns=Ns}) -> print_face(Output, Ns) end,Fs}, true}); true -> ignore end end, WriteColorIndex = fun (IdxIndent) -> - Output(IdxIndent, {tag_attr_array, "colorIndex", {fun(#e3d_face{vc=Vc}) -> print_face(Output, Vc) end,Fs}}) + Output(IdxIndent, {tag_attr_array, "colorIndex", {fun(#e3d_face{vc=Vc}) -> print_face(Output, Vc) end,Fs}, true}) end, WriteTexCoordIndex = fun (IdxIndent) -> - Output(IdxIndent, {tag_attr_array, "texCoordIndex", {fun(#e3d_face{tx=UV}) -> print_face(Output, UV) end,Fs}}) + Output(IdxIndent, {tag_attr_array, "texCoordIndex", {fun(#e3d_face{tx=UV}) -> print_face(Output, UV) end,Fs}, true}) end, Output(4, {only_x3d, [WriteCoordIndex, WriteNormalIndex] ++ if ColTab /= []-> [WriteColorIndex]; @@ -211,22 +244,25 @@ export_object(Output, #e3d_mesh{fs=Fs0,ns=NTab,vs=VTab,tx=UVTab,vc=ColTab}, end}), Output(4, {after_attrs}), + HasAfterNormal = ColTab /= [] orelse UVTab /= [], + HasAfterCoord = ExportN orelse HasAfterNormal, + %% Helpers - W3 = fun({X,Y,Z}) -> Output(6, {array_item, io_lib:format("~p ~p ~p", [X,Y,Z])}) end, - W2 = fun({U,V}) -> Output(6, {array_item, io_lib:format("~p ~p", [U,V])}) end, + W3 = fun({X,Y,Z}) -> Output(6, {array_item_vec3, {X,Y,Z}}) end, + W2 = fun({U,V}) -> Output(6, {array_item_vec2, {U,V}}) end, %% Write vertex coordinates Output(4, {start_tag, "coord", "Coordinate"}), - Output(5, {tag_attr_array, "point", {W3,VTab}}), + Output(5, {tag_attr_array, "point", {W3,VTab}, false}), Output(5, {after_attrs}), - Output(4, {close_tag, "Coordinate"}), + Output(4, {close_tag, "Coordinate", HasAfterCoord}), Output(4, {only_wrl, [WriteCoordIndex]}), if ExportN -> %% Write Normal vectors Output(4, {start_tag, "normal", "Normal"}), - Output(5, {tag_attr_array, "vector", {W3,NTab}}), + Output(5, {tag_attr_array, "vector", {W3,NTab}, false}), Output(5, {after_attrs}), - Output(4, {close_tag, "Normal"}), + Output(4, {close_tag, "Normal", HasAfterNormal}), Output(4, {only_wrl, [WriteNormalIndex]}); true -> ignore end, @@ -234,22 +270,22 @@ export_object(Output, #e3d_mesh{fs=Fs0,ns=NTab,vs=VTab,tx=UVTab,vc=ColTab}, if ColTab /= []-> % Use vertex colors Output(4, {start_tag, "color", "Color"}), - Output(5, {tag_attr_array, "color", {W3,ColTab}}), + Output(5, {tag_attr_array, "color", {W3,ColTab}, false}), Output(5, {after_attrs}), - Output(4, {close_tag, "Color"}), + Output(4, {close_tag, "Color", false}), Output(4, {only_wrl, [WriteColorIndex]}); UVTab /= [] -> % Use UV-coords Output(4, {start_tag, "texCoord", "TextureCoordinate"}), - Output(5, {tag_attr_array, "point", {W2,UVTab}}), + Output(5, {tag_attr_array, "point", {W2,UVTab}, false}), Output(5, {after_attrs}), - Output(4, {close_tag, "TextureCoordinate"}), + Output(4, {close_tag, "TextureCoordinate", false}), Output(4, {only_wrl, [WriteTexCoordIndex]}); true -> ignore end, %% Close Shape and IndexedFaceSet - Output(3, {close_tag, "IndexedFaceSet"}), - Output(2, {close_tag, "Shape"}), + Output(3, {close_tag, "IndexedFaceSet", false}), + Output(2, {close_tag, "Shape", false}), Used_mats. material(Output, Name, Mat_defs, Used, Dir) -> @@ -265,6 +301,13 @@ material(Output, Name, Mat_defs, Used, Dir) -> % Note: vrml represents ambient colour as a proportion of % diffuse colour, not in its own right. def_material(Output, Name, Mat0, Dir) -> + DiffuseMapVal = case lists:keysearch(maps, 1, Mat0) of + {value, {maps,Maps}} -> + lists:keysearch(diffuse, 1, Maps); + _ -> error + end, + HaveMap = case DiffuseMapVal of {value, _} -> true; _ -> false end, + Mat = lookup(opengl, Mat0), Output(3, {start_tag, "appearance", "Appearance"}), Output(4, {after_attrs}), @@ -272,11 +315,11 @@ def_material(Output, Name, Mat0, Dir) -> Output(4, {start_tag_defined, "material", "Material", clean_id(Name)}), {Ar, Ag, Ab, O0} = lookup(ambient, Mat), {Dr, Dg, Db, _} = lookup(diffuse, Mat), - Output(5, {tag_attr, "diffuseColor", io_lib:format("~p ~p ~p",[Dr, Dg, Db])}), + Output(5, {tag_attr_vec3, "diffuseColor", {Dr, Dg, Db}, true}), {Er, Eg, Eb, _} = lookup(emission, Mat), - Output(5, {tag_attr, "emissiveColor", io_lib:format("~p ~p ~p", [Er, Eg, Eb])}), + Output(5, {tag_attr_vec3, "emissiveColor", {Er, Eg, Eb}, true}), {Sr, Sg, Sb, _} = lookup(specular, Mat), - Output(5, {tag_attr, "specularColor", io_lib:format("~p ~p ~p",[Sr, Sg, Sb])}), + Output(5, {tag_attr_vec3, "specularColor", {Sr, Sg, Sb}, true}), case (Ar+Ag+Ab)/3 of 0.0 -> Amb = 1.0, %% wrml needs a non zero value for ambient intensity @@ -285,35 +328,31 @@ def_material(Output, Name, Mat0, Dir) -> Amb = Amb0, O = 1.0-O0 end, - Output(5, {tag_attr, "ambientIntensity", io_lib:format("~p",[Amb])}), - Output(5, {tag_attr, "transparency", io_lib:format("~p",[O])}), + Output(5, {tag_attr_float, "ambientIntensity", Amb, true}), + Output(5, {tag_attr_float, "transparency", O, true}), S = lookup(shininess, Mat), - Output(5, {tag_attr, "shininess", io_lib:format("~p",[S])}), + Output(5, {tag_attr_float, "shininess", S, false}), Output(5, {after_attrs}), - Output(4, {close_tag, "Material"}), - case lists:keysearch(maps, 1, Mat0) of - {value, {maps,Maps}} -> - case lists:keysearch(diffuse, 1, Maps) of - {value, {diffuse,#e3d_image{filename=FileName}}} -> - File = case string:prefix(FileName, Dir) of - nomatch -> FileName; - [_Delim|File0] -> File0 - end, - Output(4, {start_tag, "texture", "ImageTexture"}), - Output(5, {tag_attr_string, "url", File}), - Output(4, {close_tag_no_content, "ImageTexture"}); - _ -> - ignore - end; - _ -> ignore + Output(4, {close_tag, "Material", HaveMap}), + case DiffuseMapVal of + {value, {diffuse,#e3d_image{filename=FileName}}} -> + File = case string:prefix(FileName, Dir) of + nomatch -> FileName; + [_Delim|File0] -> File0 + end, + Output(4, {start_tag, "texture", "ImageTexture"}), + Output(5, {tag_attr_string, "url", File, false}), + Output(4, {close_tag_no_content, "ImageTexture", false}); + _ -> + ignore end, - Output(3, {close_tag, "Appearance"}). + Output(3, {close_tag, "Appearance", true}). use_material(Output, Mat) -> Output(3, {start_tag, "appearance", "Appearance"}), Output(3, {after_attrs}), Output(4, {attr_use, "material", "Material", clean_id(Mat)}), - Output(3, {close_tag, "Appearance"}). + Output(3, {close_tag, "Appearance", true}). print_face(Output, Vs) -> Output(0, {raw, " "}), @@ -360,7 +399,11 @@ all_shapes(wrl, F, IO, AccIn, [H,H1|T]) -> all_shapes(wrl, F,IO,Acc,[H1|T]); all_shapes(x3d, F, IO, AccIn, [H,H1|T]) -> Acc = F(H, AccIn), - all_shapes(wrl, F,IO,Acc,[H1|T]); + all_shapes(x3d, F,IO,Acc,[H1|T]); +all_shapes(x3dj, F, IO, AccIn, [H,H1|T]) -> + Acc = F(H, AccIn), + io:put_chars(IO, " ,\n"), + all_shapes(x3dj, F,IO,Acc,[H1|T]); all_shapes(_, F, _, AccIn, [H]) -> F(H, AccIn). @@ -419,8 +462,10 @@ to_io(wrl, F, Indent, Data) -> io:format(F, "~s~s ~s {\n",[ ?INDENTSPC(Indent), OntoAttr, Tag]); {start_tag, Tag} -> io:format(F, "~s~s {\n",[ ?INDENTSPC(Indent), Tag]); - {array_item, Str} -> - io:format(F, "~s~s",[ ?INDENTSPC(Indent), Str]); + {array_item_vec3, {V1,V2,V3}} -> + io:format(F, "~s~p ~p ~p",[ ?INDENTSPC(Indent), V1,V2,V3]); + {array_item_vec2, {V1,V2}} -> + io:format(F, "~s~p ~p",[ ?INDENTSPC(Indent), V1,V2]); {raw, Str} -> io:format(F, "~s",[Str]); {attr_use, OntoAttr, _Tag, Id} -> @@ -429,18 +474,25 @@ to_io(wrl, F, Indent, Data) -> {start_tag_defined, OntoAttr, Tag, Id} -> Id_1 = unicode:characters_to_nfc_binary(Id), io:format(F, "~s~s DEF ~s ~s {\n",[ ?INDENTSPC(Indent), OntoAttr, Id_1, Tag]); - {close_tag, _Tag} -> + {close_tag, _Tag, _} -> io:format(F, "~s}\n", [ ?INDENTSPC(Indent) ]); - {close_tag_no_content, _Tag} -> + {close_tag_no_content, _Tag, _} -> io:format(F, "~s}\n", [ ?INDENTSPC(Indent) ]); {after_attrs} -> ok; - {tag_attr, AttrName, AttrValue} -> - io:format(F, "~s~s ~s\n", [ ?INDENTSPC(Indent), AttrName, AttrValue]); - {tag_attr_string, AttrName, AttrValue} -> + {tag_attr_bool, AttrName, AttrValue, _} -> + AttrVal_1 = case AttrValue of + true -> "TRUE" + end, + io:format(F, "~s~s ~s\n", [ ?INDENTSPC(Indent), AttrName, AttrVal_1]); + {tag_attr_vec3, AttrName, {V1,V2,V3}, _} -> + io:format(F, "~s~s ~p ~p ~p\n", [ ?INDENTSPC(Indent), AttrName, V1,V2,V3]); + {tag_attr_float, AttrName, AttrValue, _} -> + io:format(F, "~s~s ~p\n", [ ?INDENTSPC(Indent), AttrName, AttrValue]); + {tag_attr_string, AttrName, AttrValue, _} -> AttrValue_1 = binary_to_list(unicode:characters_to_nfc_binary(AttrValue)), io:format(F, "~s~s ~p\n", [ ?INDENTSPC(Indent), AttrName, AttrValue_1]); - {tag_attr_array, AttrName, {WF, List}} -> + {tag_attr_array, AttrName, {WF, List}, _} -> io:format(F, "~s~s [\n", [ ?INDENTSPC(Indent), AttrName]), all(WF,F,List), io:put_chars(F, " ]\n") @@ -455,8 +507,10 @@ to_io(x3d, F, Indent, Data) -> io:format(F, "~s<~s \n",[ ?INDENTSPC(Indent), Tag]); {start_tag, Tag} -> io:format(F, "~s<~s \n",[ ?INDENTSPC(Indent), Tag]); - {array_item, Str} -> - io:format(F, "~s~s",[ ?INDENTSPC(Indent), Str]); + {array_item_vec3, {V1,V2,V3}} -> + io:format(F, "~s~p ~p ~p",[ ?INDENTSPC(Indent), V1,V2,V3]); + {array_item_vec2, {V1,V2}} -> + io:format(F, "~s~p ~p",[ ?INDENTSPC(Indent), V1,V2]); {raw, Str} -> io:format(F, "~s",[Str]); {attr_use, _OntoAttr, Tag, Id} -> @@ -465,20 +519,83 @@ to_io(x3d, F, Indent, Data) -> {start_tag_defined, _OntoAttr, Tag, Id} -> Id_1 = unicode:characters_to_nfc_binary(Id), io:format(F, "~s<~s DEF=\"~s\" \n",[ ?INDENTSPC(Indent), Tag, Id_1]); - {close_tag, Tag} -> + {close_tag, Tag, _} -> io:format(F, "~s\n", [ ?INDENTSPC(Indent), Tag ]); - {close_tag_no_content, _Tag} -> + {close_tag_no_content, _Tag, _} -> io:format(F, "~s/>\n", [ ?INDENTSPC(Indent) ]); {after_attrs} -> io:format(F, "~s>\n", [ ?INDENTSPC(Indent) ]); - {tag_attr, AttrName, AttrValue} -> - io:format(F, "~s~s=\"~s\"\n", [ ?INDENTSPC(Indent), AttrName, AttrValue]); - {tag_attr_string, AttrName, AttrValue} -> + {tag_attr_bool, AttrName, AttrValue, _} -> + AttrVal_1 = case AttrValue of + true -> "TRUE" + end, + io:format(F, "~s~s=\"~s\"\n", [ ?INDENTSPC(Indent), AttrName, AttrVal_1]); + {tag_attr_vec3, AttrName, {V1,V2,V3}, _} -> + io:format(F, "~s~s=\"~p ~p ~p\"\n", [ ?INDENTSPC(Indent), AttrName, V1,V2,V3]); + {tag_attr_float, AttrName, AttrValue, _} -> + io:format(F, "~s~s=\"~p\"\n", [ ?INDENTSPC(Indent), AttrName, AttrValue]); + {tag_attr_string, AttrName, AttrValue, _} -> Str_1 = unicode:characters_to_nfc_binary(AttrValue), io:format(F, "~s~s=\"~s\"\n", [ ?INDENTSPC(Indent), AttrName, Str_1]); - {tag_attr_array, AttrName, {WF, List}} -> + {tag_attr_array, AttrName, {WF, List}, _} -> io:format(F, "~s~s=\"\n", [ ?INDENTSPC(Indent), AttrName]), all(WF,F,List), io:put_chars(F, "\"\n") + end; +to_io(x3dj, F, Indent, Data) -> + case Data of + {only_x3d, List} -> + [Fn(Indent) || Fn <- List]; + {only_wrl, _} -> + ok; + {start_tag, OntoAttr, Tag} -> + io:format(F, "~s\"-~s\": {\"~s\": {\n",[ ?INDENTSPC(Indent), OntoAttr, Tag]); + {start_tag, Tag} -> + io:format(F, "~s{\"~s\": {\n",[ ?INDENTSPC(Indent), Tag]); + {array_item_vec3, {V1,V2,V3}} -> + io:format(F, "~s~p,~p,~p",[ ?INDENTSPC(Indent), V1,V2,V3]); + {array_item_vec2, {V1,V2}} -> + io:format(F, "~s~p,~p",[ ?INDENTSPC(Indent), V1,V2]); + {raw, Str} -> + io:format(F, "~s",[Str]); + {attr_use, OntoAttr, Tag, Id} -> + Id_1 = unicode:characters_to_nfc_binary(Id), + io:format(F, "~s\"-~s\":{\"~s\": {\"@USE\":\"~s\"}}\n",[ ?INDENTSPC(Indent), OntoAttr, Tag, Id_1]); + {start_tag_defined, OntoAttr, Tag, Id} -> + Id_1 = unicode:characters_to_nfc_binary(Id), + io:format(F, "~s\"-~s\":{\"~s\": {\"@DEF\":\"~s\", \n",[ ?INDENTSPC(Indent), OntoAttr, Tag, Id_1]); + {close_tag, _Tag, AddComma} -> + io:format(F, "~s} }~s\n", [ ?INDENTSPC(Indent),cma(AddComma) ]); + {close_tag_no_content, _Tag, AddComma} -> + io:format(F, "~s} }~s\n", [ ?INDENTSPC(Indent),cma(AddComma) ]); + {after_attrs} -> + ok; + {tag_attr_bool, AttrName, AttrValue, AddComma} -> + AttrVal_1 = case AttrValue of + true -> "1" + end, + io:format(F, "~s\"@~s\": ~s~s\n", [ + ?INDENTSPC(Indent), AttrName, AttrVal_1, cma(AddComma)]); + {tag_attr_vec3, AttrName, {V1,V2,V3}, AddComma} -> + io:format(F, "~s\"@~s\": [~p,~p,~p]~s\n", [ + ?INDENTSPC(Indent), AttrName, V1,V2,V3, cma(AddComma)]); + {tag_attr_float, AttrName, AttrValue, AddComma} -> + io:format(F, "~s\"@~s\": ~p~s\n", [ + ?INDENTSPC(Indent), AttrName, AttrValue, cma(AddComma)]); + {tag_attr_string, AttrName, AttrValue, AddComma} -> + AttrValue_1 = binary_to_list(unicode:characters_to_nfc_binary(AttrValue)), + io:format(F, "~s\"@~s\": ~p~s\n", [ + ?INDENTSPC(Indent), AttrName, AttrValue_1, cma(AddComma)]); + {tag_attr_array, AttrName, {WF, List}, AddComma} -> + io:format(F, "~s\"@~s\":[\n", [ ?INDENTSPC(Indent), AttrName]), + all(WF,F,List), + io:format(F, " ]~s\n", [cma(AddComma)]) end. + +cma(true) -> + ","; +cma(false) -> + "". + + diff --git a/plugins_src/import_export/x3d_import.erl b/plugins_src/import_export/x3d_import.erl index 002ebeb18..bb0a534a0 100644 --- a/plugins_src/import_export/x3d_import.erl +++ b/plugins_src/import_export/x3d_import.erl @@ -14,7 +14,7 @@ -module(x3d_import). -export([init_import/0,do_import/2]). --export([t_iv/0, t_x3d/0, t_vrml/0]). +-export([t_iv/0, t_x3d/0, t_vrml/0, t_x3dj/0]). -import(lists, [map/2,foldl/3,keydelete/3,keyreplace/4,sort/1]). @@ -36,6 +36,8 @@ -record(geometry, { coords = [], coordIndices = [], + normal = [], + normalIndices = [], texCoords = none, tcIndices = none, colors = none, @@ -54,7 +56,7 @@ -record(shape_piece, { appearance = none, - geometry = {[], []} + geometry :: #geometry{} }). -record(material, { material, @@ -85,6 +87,12 @@ props() -> [{".x3d", "X3D File"}, {".x3dz", "X3D File Compressed"}, {".x3d.gz", "X3D File (gzipped)"}, + {".x3dj", "X3D JSON File"}, + {".x3djz", "X3D JSON File Compressed"}, + {".x3dj.gz", "X3D JSON File (gzipped)"}, + {".x3dv", "ClassicVRML File"}, + {".x3dvz", "ClassicVRML File Compressed"}, + {".x3dv.gz", "ClassicVRML File (gzipped)"}, {".wrl", "VRML World"}, {".wrz", "VRML World Compressed"}, {".wrl.gz", "VRML World (gzipped)"}, @@ -154,25 +162,38 @@ combine_shape_pieces([], Grp) -> lists:reverse(Grp); combine_shape_pieces([Shapes], Grp) -> lists:reverse([[Shapes]|Grp]); -combine_shape_pieces([#shape_piece{geometry=Geom1}=Shape1|ShapesList], Grp) -> - #geometry{coords=Coords1,coordIndices=Coords1I}=Geom1, - CoordPairs1 = reverse_coord_pairs(Coords1,Coords1I), - combine_shape_pieces_1({Shape1, sets:from_list(CoordPairs1)}, ShapesList, [], [], Grp). -combine_shape_pieces_1(Shp1, [Shape2|ShapesList], SameShape, Other, Grp) -> +combine_shape_pieces([Shape1|ShapesList], Grp) -> + CoordPairs1 = rv_coordpairs_from_shapes([Shape1]), + combine_shape_pieces_1({Shape1, CoordPairs1}, ShapesList, [], [], Grp, false). +combine_shape_pieces_1(Shp1, [Shape2|ShapesList], SameShape, Other, Grp, Found) -> case combine_shape_pieces_same(Shp1, Shape2) of true -> - combine_shape_pieces_1(Shp1, ShapesList, [Shape2|SameShape], Other, Grp); + {Shape1,_}=Shp1, + combine_shape_pieces_1( + {Shape1,rv_coordpairs_from_shapes([Shape1,Shape2|SameShape])}, + ShapesList, [Shape2|SameShape], Other, Grp, true); false -> - combine_shape_pieces_1(Shp1, ShapesList, SameShape, [Shape2|Other], Grp) + combine_shape_pieces_1(Shp1, ShapesList, SameShape, [Shape2|Other], Grp, Found) end; -combine_shape_pieces_1({Shape1, _}, [], SameShape, Other, Grp) -> - combine_shape_pieces(Other, [lists:reverse([Shape1|SameShape])|Grp]). +combine_shape_pieces_1({Shape1, _}, [], SameShape, Other, Grp, false) -> + combine_shape_pieces(Other, [lists:reverse([Shape1|SameShape])|Grp]); +combine_shape_pieces_1(Shp1, [], SameShape, Other, Grp, true) -> + %% Try again with the other shapes + combine_shape_pieces_1(Shp1, Other, SameShape, [], Grp, false). combine_shape_pieces_same({_,Coords1Set},#shape_piece{geometry=Geom2}=_) -> #geometry{coords=Coords2,coordIndices=Coords2I}=Geom2, combine_shape_pieces_same_1(Coords1Set, coord_pairs(Coords2, Coords2I)). combine_shape_pieces_same_1(Coords1Set, CoordsPair2) -> not sets:is_disjoint(Coords1Set, sets:from_list(CoordsPair2)). - + +rv_coordpairs_from_shapes(SL) -> + CoordPairs = lists:append([rv_coordpairs_from_shapes_1(S) || S <- SL]), + sets:from_list(CoordPairs). +rv_coordpairs_from_shapes_1(#shape_piece{geometry=Geom1}) -> + #geometry{coords=Coords1,coordIndices=Coords1I}=Geom1, + reverse_coord_pairs(Coords1,Coords1I). + + reverse_coord_pairs(Coords, Indices) -> [{E2,E1} || {E1,E2} <- coord_pairs(Coords, Indices)]. @@ -180,6 +201,8 @@ coord_pairs(Coords, Indices) -> Arr = array:from_list(Coords), [{array:get(I1,Arr),array:get(I2,Arr)} || {I1,I2} <- all_edges(Indices)]. + + fill_in_colorlist(Fs0, List) -> fill_in_txlist(Fs0, List). @@ -211,25 +234,32 @@ shape_to_object(ShortFilename, ShapePieces, X3DFullPath) -> ShapeId = "_" ++ integer_to_list(abs(erlang:unique_integer())), ObjectName = ShortFilename ++ ShapeId, - {Mat1_2, Vs_2, Colors_2, TxList_2, Fs0L_2, _,_,_} = lists:foldl(fun(Shape, - {Mat1_0, Vs_0, Colors_0, TxList_0, Fs0L_0, VsOffset,TxOffset,ColOffset}) - -> - {Mat1_1, Vs_1, Colors_1, TxList_1, Fs0L_1, VsOffset_1, TxOffset_1, ColOffset_1} = - shape_piece_for_object(ObjectName, Shape, VsOffset, TxOffset, ColOffset, X3DFullPath), - {[Mat1_1|Mat1_0], [Vs_1|Vs_0], [Colors_1|Colors_0], [TxList_1|TxList_0], [Fs0L_1|Fs0L_0], - VsOffset_1, TxOffset_1, ColOffset_1 } - end, {[],[],[],[],[],0,0,0}, ShapePieces), + [#shape_piece{geometry=#geometry{creaseAngle=CrAng}}|_] = ShapePieces, + + {Mat1_2, Vs_2, Ns_2, Colors_2, TxList_2, Fs0L_2, _,_,_,_} = lists:foldl( + fun(Shape, + {Mat1_0, Vs_0, Ns_0, Colors_0, TxList_0, Fs0L_0, VsOffset,NsOffset,TxOffset,ColOffset}) + -> + {Mat1_1, Vs_1, Ns_1, Colors_1, TxList_1, Fs0L_1, + VsOffset_1, NsOffset_1, TxOffset_1, ColOffset_1} = + shape_piece_for_object(ObjectName, Shape, + VsOffset, NsOffset, TxOffset, ColOffset, X3DFullPath), + {[Mat1_1|Mat1_0], [Vs_1|Vs_0], [Ns_1|Ns_0], + [Colors_1|Colors_0], [TxList_1|TxList_0], [Fs0L_1|Fs0L_0], + VsOffset_1, NsOffset_1, TxOffset_1, ColOffset_1 } + end, {[],[],[],[],[],[],0,0,0,0}, ShapePieces), Mat1 = lists:reverse(lists:filter( fun(none) -> false; (_) -> true end, Mat1_2)), Vs = lists:append(lists:reverse(Vs_2)), + Ns = lists:append(lists:reverse(Ns_2)), Vc = lists:append(lists:reverse(Colors_2)), TxList = lists:append(lists:reverse(TxList_2)), Fs0L = lists:append(lists:reverse(Fs0L_2)), - HEs = all_edges([L || {L, _, _, _} <- Fs0L]), Efs = [ #e3d_face{ vs=L, + ns=NL, tx=LTx, vc=C, mat= @@ -237,15 +267,17 @@ shape_to_object(ShortFilename, ShapePieces, X3DFullPath) -> none -> []; _ -> [MatName] end - } || {L, C, LTx, MatName} <- Fs0L], + } || {L, NL, C, LTx, MatName} <- Fs0L], - Mesh = #e3d_mesh{ + %% Put into a mesh and apply the crease angle if needed + Mesh = apply_crease(#e3d_mesh{ type=polygon, vs=Vs, + ns=Ns, vc=Vc, fs=Efs, - he=HEs, - tx=TxList }, + %%he=HEs, + tx=TxList }, CrAng), ?DEBUG_FMT("Mesh=~p~n", [Mesh]), @@ -254,7 +286,7 @@ shape_to_object(ShortFilename, ShapePieces, X3DFullPath) -> shape_piece_for_object(ObjectName, #shape_piece{appearance=Appearance,geometry=Geometry}=_Shape, - VsOffset, TxOffset, ColOffset, X3DFullPath) + VsOffset, NsOffset, TxOffset, ColOffset, X3DFullPath) -> case Appearance of #material{ material=MatPs, texture=Filename} -> @@ -263,10 +295,20 @@ shape_piece_for_object(ObjectName, MatName = none, Mat1 = none end, - #geometry{coords=Vs,coordIndices=Fs0_0,texCoords=TxList_0, + #geometry{coords=Vs,coordIndices=Fs0_0, + normal=Ns_0,normalIndices=Fs0Ns_0, + texCoords=TxList_0, tcIndices=Fs0Tx_0,colors=Colors_0,colIndices=ColIndices_0, creaseAngle=_CreaseAngle} = Geometry, Fs0 = [ [F+VsOffset || F <- FL] || FL <- Fs0_0], + case Ns_0 of + none -> + Ns = [], + NsIndices = []; + _ -> + Ns = Ns_0, + NsIndices = [[N+NsOffset || N <- NL] || NL <- Fs0Ns_0] + end, case Colors_0 of none -> Colors = [], @@ -280,22 +322,107 @@ shape_piece_for_object(ObjectName, none -> TxList = []; _ -> TxList = TxList_0 end, - Fs0Tx_1 = case Fs0Tx_0 of none -> none; _ -> [ [T+TxOffset || T <- TL] || TL <- Fs0Tx_0] end, + Fs0Tx_1 = case Fs0Tx_0 of + none -> none; + _ -> [ [T+TxOffset || T <- TL] || TL <- Fs0Tx_0] + end, Fs0Tx = fill_in_txlist(Fs0, Fs0Tx_1), MatNames = fill_in_matname(Fs0, MatName), VsOffset_1 = length(Vs) + VsOffset, + NsOffset_1 = length(Ns) + NsOffset, TxOffset_1 = length(TxList) + TxOffset, ColOffset_1 = length(Colors) + ColOffset, - {Mat1, Vs, Colors, TxList, zip_face_elems(Fs0, ColIndices_1, Fs0Tx, MatNames), - VsOffset_1, TxOffset_1, ColOffset_1}. - -zip_face_elems(A,B,C,D) -> - zip_face_elems(A,B,C,D,[]). -zip_face_elems([A|AR],[B|BR],[C|CR],[D|DR], O) -> - zip_face_elems(AR,BR,CR,DR, [{A,B,C,D}|O]); -zip_face_elems([],[],[],[], O) -> + {Mat1, Vs, Ns, Colors, TxList, zip_face_elems(Fs0, NsIndices, ColIndices_1, Fs0Tx, MatNames), + VsOffset_1, NsOffset_1, TxOffset_1, ColOffset_1}. + +zip_face_elems(A,Ns,B,C,D) -> + zip_face_elems(A,Ns,B,C,D,[]). +zip_face_elems([A|AR],NsL_0,CL_0,TxL_0,MtL_0, O) -> + {NsL,Ns} = case NsL_0 of + [] -> {[], []}; + [Ns_2|NsL_2] -> {NsL_2,Ns_2} + end, + {CL,C} = case CL_0 of + [] -> {[], []}; + [C_2|CL_2] -> {CL_2,C_2} + end, + {TxL,T} = case TxL_0 of + [] -> {[], []}; + [T_2|TxL_2] -> {TxL_2,T_2} + end, + {MtL,D} = case MtL_0 of + [] -> {[], []}; + [D_2|MtL_2] -> {MtL_2,D_2} + end, + zip_face_elems(AR,NsL,CL,TxL,MtL, [{A,Ns,C,T,D}|O]); +zip_face_elems([],[],[],[],[], O) -> lists:reverse(O). - + + +%% Use creaseAngle to set hard edges if there are no normals yet. +%% +apply_crease(#e3d_mesh{ns=[_|_],fs=[#e3d_face{ns=[_|_]}|_]}=Mesh0, _) -> + Mesh0; +apply_crease(Mesh_0, Ang_0) -> + %% Need to remove duplicate vertices first. + #e3d_mesh{vs=VS,fs=Efs}=Mesh=e3d_mesh:merge_vertices(Mesh_0), + io:format("X3D: NOTE: Applying creaseAngle~n",[]), + try + Ang = math:cos(Ang_0), + Arr = array:from_list(VS), + SNrm_0 = [apply_crease_1(V, Arr) || #e3d_face{vs=V} <- Efs], + SNrm = apply_crease_3(SNrm_0), + ELst = lists:append([EL || {EL,_} <- SNrm_0]), + HE = apply_crease_4(ELst, SNrm, Ang), + Mesh#e3d_mesh{he=HE} + catch ErrCls:Err:ST -> + io:format("X3D: ------------------------------------~n",[]), + io:format("X3D: ERROR: creaseAngle failed: ~p:~p~n~p~n",[ErrCls,Err,ST]), + io:format("X3D: ------------------------------------~n",[]), + Mesh + end. + +apply_crease_1([V1,V2,V3|_]=VIdx, Arr) -> + Norm = e3d_vec:normal(array:get(V1, Arr), array:get(V2, Arr), array:get(V3, Arr)), + {apply_crease_2(VIdx), Norm}. + +apply_crease_2([V1|_]=L) -> + apply_crease_2(L, V1). +apply_crease_2([V1], B1) -> + [{V1,B1}]; +apply_crease_2([V1|[V2|_]=L], B1) -> + [{V1,V2}|apply_crease_2(L, B1)]. + +apply_crease_3(L) -> + apply_crease_3(L, gb_trees:empty()). +apply_crease_3([], SNrm) -> + SNrm; +apply_crease_3([{Edges, Norm}|L], Set0) -> + apply_crease_3(L, lists:foldl( + fun(E, Set) -> + gb_trees:insert(E, Norm, Set) + end, Set0, Edges)). + +%% Used wings_body:auto_smooth/5 as reference for this. +apply_crease_4(ELst, SNrm, Ang) -> + lists:foldl(fun (E, HE) -> apply_crease_4_2(E, SNrm, Ang, HE) end, [], ELst). +apply_crease_4_2({V1,V2}, SNrm, Ang, HE) -> + Nrm1 = gb_trees:get({V1,V2},SNrm), + Nrm2 = gb_trees:get({V2,V1},SNrm), + case e3d_vec:is_zero(Nrm1) orelse e3d_vec:is_zero(Nrm2) of + true -> + HE; + _ -> + case e3d_vec:dot(Nrm1, Nrm2) of + DAng when DAng < Ang -> + %% Hard + [{V1,V2}|HE]; + _ -> + %% Soft + HE + end + end. + edge_pairs([E|_]=Fs) -> edge_pairs(Fs, E, []). @@ -308,25 +435,39 @@ all_edges(FL) -> lists:append([edge_pairs(F) || F <- FL]). -appearance_opengl(#materialprops{ +appearance_opengl(MatPs) -> + OpenGL = {opengl, + appearance_opengl_1(MatPs) ++ + [ + {metallic,0.1}, + {roughness,0.8}, + {vertex_colors, set} + ]}, + {ok, OpenGL}. + +appearance_opengl_1(#materialprops{ ambient_intensity=AmbInt, specular_color=SpecCol, shininess=Shine, diffuse_color=DifCol, transparency=Transparency, emissive_color=EmCol -}=_MatPs) -> - OpenGL = {opengl, [ +}) -> + [ {ambient, intensity_to_rgba(AmbInt)}, {specular, rgb_to_rgba(SpecCol)}, {shininess, Shine}, {diffuse, rgb_to_rgba(DifCol, 1.0 - Transparency)}, - {emission, rgb_to_rgba(EmCol)}, - {metallic,0.1}, - {roughness,0.8}, - {vertex_colors, set} - ]}, - {ok, OpenGL}. + {emission, rgb_to_rgba(EmCol)} + ]; +appearance_opengl_1(none) -> + [ + {ambient,{0.0,0.0,0.0,0.0}}, + {specular, {0.2,0.2,0.2,1.0}}, + {shininess,0.2}, + {diffuse, {0.8,0.8,0.8,1.0}}, + {emission,{0.0,0.0,0.0,1.0}} + ]. appearance_to_material(ObjectName, MatPs, none, VsOffset, _FullPath) -> {ok, OpenGL} = appearance_opengl(MatPs), @@ -356,7 +497,7 @@ load_texture_maps(Filename_0, _Id, X3DFullPath) -> {error, Err} -> %% Image could not be loaded. io:format("X3D: " ++ ?__(1,"INFO: Texture could not be loaded:") ++ - " ~p: ~p", [Filename_0, Err]), + " ~p: ~p~n", [Filename_0, Err]), {ok, [{maps, []}]} end. @@ -421,14 +562,20 @@ dialog(import) -> get_file_type(FileName) -> case string:to_lower(filename:extension(FileName)) of - ".x3d" ++ _ -> x3d; - ".wrl" ++ _ -> wrl; + ".json" ++ _ -> x3dj; + ".x3dj" ++ _ -> x3dj; + ".x3dv" ++ _ -> wrl; + ".x3d" ++ _ -> x3d; + ".wrl" ++ _ -> wrl; ".wrz" -> wrl; ".iv" -> wrl; ".gz" ++ _ -> case string:to_lower(filename:extension(filename:rootname(FileName))) of - ".x3d" ++ _ -> x3d; - ".wrl" ++ _ -> wrl; + ".json" ++ _ -> x3dj; + ".x3dj" ++ _ -> x3dj; + ".x3dv" ++ _ -> wrl; + ".x3d" ++ _ -> x3d; + ".wrl" ++ _ -> wrl; ".wrz" -> wrl; ".iv" -> wrl end @@ -442,6 +589,14 @@ read_file_content(x3d, Filename) -> C end || Cont <- Conts]), {ok, ShapeList}; +read_file_content(x3dj, Filename) -> + {ok, File} = file:read_file(Filename), + {ok, Conts} = read_x3djson_content(File), + ShapeList = lists:flatten([ begin + {ok, C} = trav(def_or_use_var(Cont)), + C + end || Cont <- Conts]), + {ok, ShapeList}; read_file_content(wrl, Filename) -> {ok, File} = file:read_file(Filename), case read_vrml_content(File) of @@ -885,6 +1040,168 @@ x3d_sub_of(_) -> root. +%% +%% Parse X3D JSON +%% + +read_x3djson_content(Bin_0) -> + case Bin_0 of + <<31,139,_/binary>> -> + %% A gz header, the file is compressed. + read_x3djson_content_1(zlib:gunzip(Bin_0)); + _ -> + %% Uncompressed + read_x3djson_content_1(Bin_0) + end. +read_x3djson_content_1(C) -> + {ok, x3djson_0(json_data(jsone:decode(C, [{object_format,tuple}])))}. + +x3djson_0({a,[{o,[{Tag,{o,{a,C}}}]}|_]}) when is_list(C) -> + x3djson_0_1(Tag, C); +x3djson_0({o,[{Tag,{o,C}}]}) when is_list(C) -> + x3djson_0_1(Tag, C). + +x3djson_0_1(Tag, C) -> + case string:uppercase(binary_to_list(Tag)) =:= "X3D" of + true -> + {o,L}=proplists:get_value(<<"Scene">>, C, none), + {a,CL} = proplists:get_value(<<"-children">>, L, none), + [ case x3djson_tag(E) of + {def,Name,{container,CTag,Cont}} -> + {def,Name,{CTag,Cont}}; + {use,Name} -> + {use,Name}; + {container,CTag,Cont} -> + {CTag,Cont} + end || E <- CL ] + end. + +x3djson_tag({o,[{Tag,{o,L}}]}) -> + x3djson_tag(Tag, L); +x3djson_tag({Tag,{o,L}}) -> + x3djson_tag(Tag, L). +x3djson_tag(Tag, L) when is_list(L) -> + x3djson_tag_1(Tag, L, none, []). +x3djson_tag_1(Tag, [], DefUse, OL) -> + ContainerName = Tag, + Fields = lists:reverse(OL), + case DefUse of + none -> + {container, ContainerName, Fields}; + {def, Str} -> + {def, Str, {container, ContainerName, Fields}}; + {use, Str} when Fields =:= [] -> + {use, Str} + end; +x3djson_tag_1(Tag, [{<<"@DEF">>,{s,C}}|L], _, OL) -> + x3djson_tag_1(Tag, L, {def, C}, OL); +x3djson_tag_1(Tag, [{<<"@USE">>,{s,C}}|L], _, OL) -> + x3djson_tag_1(Tag, L, {use, C}, OL); +%% Attribute value +x3djson_tag_1(Tag, [{<<"@",FName/binary>>,C_0}|L], DefUse, OL) -> + OL_1 = [x3djson_attr(Tag, FName, C_0)|OL], + x3djson_tag_1(Tag, L, DefUse, OL_1); +%% Containers +x3djson_tag_1(Tag, [{<<"-",FName/binary>>,C_0}|L], DefUse, OL) -> + case C_0 of + {a, ValL_0} when is_list(L) -> + case remove_cmt_objs(ValL_0) of + [] -> + x3djson_tag_1(Tag, L, DefUse, OL); + ValL_1 when is_list(ValL_1) -> + ValL_2 = [x3djson_tag(E) || E <- ValL_1], + OL_1 = [ + {{field, FName}, + x3djson_tag_field_s_or_m(Tag, FName, ValL_2)} + | OL], + x3djson_tag_1(Tag, L, DefUse, OL_1) + end; + {o, _}=Val1 -> + Val1_1 = x3djson_tag(Val1), + OL_1 = [ + {{field, FName}, + x3djson_tag_field_s_or_m(Tag, FName, [Val1_1])} + | OL], + x3djson_tag_1(Tag, L, DefUse, OL_1) + end; +%% Comment +x3djson_tag_1(Tag, [{<<"#",_/binary>>,_}|L], DefUse, OL) -> + x3djson_tag_1(Tag, L, DefUse, OL). + + +x3djson_attr(Tag, FName, Val) -> + %% Determine type of field and whether to tokenize it. + FieldType = expected_field_type(Tag, {word, FName}), + case x3d_xatr_pt(Tag, FName, FieldType) of + string -> + %% Verbatim XML attribute not used by the X3D parser. + {{user, FName}, {string, Val}}; + word -> + %% Change the JSON value into a token list + Tokens = json_to_tok(Val), + case FieldType of + {multival, _} -> + %% We want the field parser to use as much of the XML attribute as + %% possible, so add brackets around the tokens. + {ok, FA, []} = parse_field(FieldType, + {word, FName}, + [open_bracket] ++ Tokens ++ [close_bracket]); + _ -> + {ok, FA, []} = parse_field(FieldType, + {word, FName}, + Tokens) + end, + FA + end. + +%% Change the JSON value into a token list that can be used +%% by parse_field/3. +%% +json_to_tok({s,Str}) -> + [{string,Str}]; +json_to_tok({a,List}) -> + [json_to_tok_1(E) || E <- List]; +json_to_tok({number,Number}) -> + [{number,Number}]. + +json_to_tok_1({s,Str}) -> + {string,Str}; +json_to_tok_1({number,Number}) -> + {number,Number}. + +%% Remove comment "objects" in the X3D JSON file. +%% +remove_cmt_objs(L) -> + remove_cmt_objs(L, []). +remove_cmt_objs([], OL) -> + lists:reverse(OL); +remove_cmt_objs([{o,[{<<"#",_/binary>>,_}]}|L], OL) -> + remove_cmt_objs(L, OL); +remove_cmt_objs([O|L], OL) -> + remove_cmt_objs(L, [O|OL]). + + +x3djson_tag_field_s_or_m(ContT, FName, [Item|_]=List) -> + case expected_field_type(ContT, {word, FName}) of + {multival, _} -> {multival, List}; + _ -> Item + end. + + +json_data({A}) -> + {o,[{E1,json_data(E2)} || {E1,E2} <- A]}; +json_data([_|_]=A) -> + {a,[json_data(E) || E <- A]}; +json_data(A) when is_binary(A) -> + {s,binary_to_list(A)}; +json_data(A) when is_number(A) -> + {number,A}; +json_data(false) -> + {number,0}; +json_data(true) -> + {number,1}. + + %% %% VRML File %% @@ -966,15 +1283,15 @@ strip_comments_outside_string(Content, AL) -> case binary:split(Content, <<10>>) of [ThisLine, R] -> case binary:split(ThisLine, <<$#>>) of - [Keep, _] -> strip_comments_outside_string(R, [Keep | AL]); - [Keep] -> strip_comments_outside_string(R, [Keep | AL]) + [Keep, _] -> strip_comments_outside_string(R, [<<10>>,Keep | AL]); + [Keep] -> strip_comments_outside_string(R, [<<10>>,Keep | AL]) end; [LastLine] -> %% Only the last line matters whether a comment character appears %% which overrides the string afterwards. case binary:split(LastLine, <<$#>>) of - [Keep, _] -> {had_comment, iolist_to_binary(lists:reverse([Keep|AL]))}; - [Keep] -> {no_comment, iolist_to_binary(lists:reverse([Keep|AL]))} + [Keep, _] -> {had_comment, iolist_to_binary(lists:reverse([<<10>>,Keep|AL]))}; + [Keep] -> {no_comment, iolist_to_binary(lists:reverse([<<10>>,Keep|AL]))} end end. @@ -1110,6 +1427,10 @@ header(<<"#VRML ", Rest_0/binary>>) -> Encoding = <<"utf-8">>, {ok, noheader, Encoding} end; +header(<<"#X3D ", Rest_0/binary>>) -> + {ok, _, Rest_1} = header_version(Rest_0), + {ok, Encoding} = header_encoding(Rest_1), + {ok, vrml2, Encoding}; header(<<"#Inventor ", Rest_0/binary>>) -> %% Try using the VRML 1.0 parser when this file header is encountered. case header_version(Rest_0) of @@ -1173,7 +1494,11 @@ parse(T, Cont) -> parse(Rest1, Cont); [{word,<<"EXTERNPROTO">>}, {word,ExtProtoName} | Rest0] -> {ok, Rest1} = parse_skip_externproto(ExtProtoName, Rest0), - parse(Rest1, Cont) + parse(Rest1, Cont); + [{word,<<"PROFILE">>},{word,_}|Rest0] -> + parse(Rest0, Cont); + [{word,<<"META">>},{string,_},{string,_}|Rest0] -> + parse(Rest0, Cont) end. @@ -1426,7 +1751,7 @@ expected_field_type(<<"TextureCoordinate">>, {word, <<"point">>}) -> expected_field_type(<<"TextureTransform">>, {word, F}) -> case F of <<"center">> -> vec2; - <<"rotation">> -> rotation; + <<"rotation">> -> float; <<"scale">> -> vec2; <<"translation">> -> vec2; _ -> any @@ -2185,20 +2510,6 @@ trav_geom_extrusion(Fields) -> trav_geom_indexedfaceset(Fields) -> - case proplists:get_value({field,<<"color">>}, Fields, none) of - none -> - Colors = none; - Cont_Col -> - {ok, Colors_0} = trav_color(def_or_use_var(Cont_Col)), - case proplists:get_value({field,<<"colorIndex">>}, Fields, none) of - none -> - ColorIndices = none; - {multival, Vals_ColI} -> - ColorIndices = delim_indexes_to_lists([ N || {float,N} <- Vals_ColI ]) - end, - HasColorPerVertex = value_from_field(<<"colorPerVertex">>, float, Fields, 1), - Colors = {to_bool(HasColorPerVertex), Colors_0, ColorIndices} - end, case proplists:get_value({field,<<"coord">>}, Fields, none) of %none -> % Coords = []; @@ -2219,21 +2530,20 @@ trav_geom_indexedfaceset(Fields) -> {multival, Vals_CI} -> CoordIndices = delim_indexes_to_lists([ N || {float,N} <- Vals_CI ]) end, + + %% creaseAngle is in radians CreaseAngle = value_from_field(<<"creaseAngle">>, float, Fields, 0.0), - case proplists:get_value({field,<<"normal">>}, Fields, none) of - none -> - _Normals = none; - Cont_N -> - {ok, _Normals} = trav_norm(def_or_use_var(Cont_N)) - end, - case proplists:get_value({field,<<"normalIndex">>}, Fields, none) of - none -> - _NormalIndices = none; - {multival, Vals_N} -> - _NormalIndices = delim_indexes_to_lists([ E || {float, E} <- Vals_N]) + HasNormalPerVertex = value_from_field(<<"normalPerVertex">>, float, Fields, 1), + case HasNormalPerVertex of + 1 -> + {Normals, NormalIndices} = + trav_geom_indexedfaceset_ns(Fields, Coords, CoordIndices); + _ -> + Normals = none, + NormalIndices = none end, - _HasNormalPerVertex = value_from_field(<<"normalPerVertex">>, float, Fields, 1), + _IsSolid = value_from_field(<<"solid">>, float, Fields, 1), case proplists:get_value({field,<<"texCoordIndex">>}, Fields, none) of @@ -2245,27 +2555,62 @@ trav_geom_indexedfaceset(Fields) -> TCIndices = delim_indexes_to_lists([ E || {float, E} <- Vals_TC]) end, - case Colors of - none -> - Colors_1 = none, - ColorIndices_1 = none; - {false, Colors_0_F, [ColorIndices_0_F]} -> - {Colors_1, ColorIndices_1} = - geom_per_face_colors(Colors_0_F, ColorIndices_0_F, CoordIndices); - {true, Colors_0_V, ColorIndices_0_V} -> - {Colors_1, ColorIndices_1} = - geom_per_vertex_colors(Colors_0_V, ColorIndices_0_V, CoordIndices) - end, + {Colors_1, ColorIndices_1} = + trav_geom_indexedfaceset_col(Fields, Coords, CoordIndices), {ok, set_ccw(to_bool(IsCCW), #geometry{ coords=Coords, coordIndices=CoordIndices, + normal=Normals, + normalIndices=NormalIndices, texCoords=TexCoords, tcIndices=TCIndices, colors=Colors_1, colIndices=ColorIndices_1, creaseAngle=CreaseAngle})}. +trav_geom_indexedfaceset_ns(Fields, Coords, CoordIndices) -> + case proplists:get_value({field,<<"normal">>}, Fields, none) of + none -> + Normals = none, + NormalIndices = none; + Cont_N -> + {ok, Normals_1} = trav_norm(def_or_use_var(Cont_N)), + case proplists:get_value({field,<<"normalIndex">>}, Fields, none) of + none when length(Coords) =:= length(Normals_1) -> + Normals = Normals_1, + NormalIndices = CoordIndices; + {multival, Vals_N} -> + Normals = Normals_1, + NormalIndices = delim_indexes_to_lists([ E || {float, E} <- Vals_N]) + end + end, + {Normals, NormalIndices}. + +trav_geom_indexedfaceset_col(Fields, _Coords, CoordIndices) -> + case proplists:get_value({field,<<"color">>}, Fields, none) of + none -> + Colors = none; + Cont_Col -> + {ok, Colors_0} = trav_color(def_or_use_var(Cont_Col)), + case proplists:get_value({field,<<"colorIndex">>}, Fields, none) of + none -> + ColorIndices = none; + {multival, Vals_ColI} -> + ColorIndices = delim_indexes_to_lists([ N || {float,N} <- Vals_ColI ]) + end, + HasColorPerVertex = value_from_field(<<"colorPerVertex">>, float, Fields, 1), + Colors = {to_bool(HasColorPerVertex), Colors_0, ColorIndices} + end, + case Colors of + none -> + {none, none}; + {false, Colors_0_F, [ColorIndices_0_F]} -> + geom_per_face_colors(Colors_0_F, ColorIndices_0_F, CoordIndices); + {true, Colors_0_V, ColorIndices_0_V} -> + geom_per_vertex_colors(Colors_0_V, ColorIndices_0_V, CoordIndices) + end. + trav_coord({container,<<"Coordinate">>,Fields}) -> case proplists:get_value({field,<<"point">>}, Fields, none) of @@ -2889,9 +3234,11 @@ read_default(FileName) -> -spec set_ccw(boolean(), #geometry{}) -> #geometry{}. set_ccw(true, AlreadyCCW) -> AlreadyCCW; -set_ccw(_, #geometry{coordIndices=CoordIndices,tcIndices=TCIndices}=Geom) -> +set_ccw(_, #geometry{coordIndices=CoordIndices,normalIndices=NormalIndices, + tcIndices=TCIndices}=Geom) -> Geom#geometry{ coordIndices=[lists:reverse(L) || L <- CoordIndices], + normalIndices=[lists:reverse(L) || L <- NormalIndices], tcIndices=[lists:reverse(L) || L <- TCIndices]}. %%% @@ -3539,6 +3886,10 @@ t_x3d() -> {ok, File} = file:read_file("examples_VRML\\x3d.x3d"), {ok, [Cont]} = read_x3d_content(File), trav(def_or_use_var(Cont)). +t_x3dj() -> + {ok, C} = file:read_file("box.x3dj"), + {ok, [Cont]} = read_x3djson_content(C), + trav(def_or_use_var(Cont)). t_vrml() -> {ok, File} = file:read_file("examples_VRML\\wrl.wrl"), {ok, [Cont]} = read_vrml_content(File), From 7ea6ca4598c042695d85523bc6e5f3ccba5d788f Mon Sep 17 00:00:00 2001 From: Edward Blake Date: Fri, 10 May 2024 10:48:38 -0400 Subject: [PATCH 2/2] wings_util.erl: Added sets_new() and sets_from_list(List) --- plugins_src/import_export/x3d_import.erl | 6 ++++-- src/wings_util.erl | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins_src/import_export/x3d_import.erl b/plugins_src/import_export/x3d_import.erl index bb0a534a0..7df6a2b87 100644 --- a/plugins_src/import_export/x3d_import.erl +++ b/plugins_src/import_export/x3d_import.erl @@ -184,11 +184,13 @@ combine_shape_pieces_same({_,Coords1Set},#shape_piece{geometry=Geom2}=_) -> #geometry{coords=Coords2,coordIndices=Coords2I}=Geom2, combine_shape_pieces_same_1(Coords1Set, coord_pairs(Coords2, Coords2I)). combine_shape_pieces_same_1(Coords1Set, CoordsPair2) -> - not sets:is_disjoint(Coords1Set, sets:from_list(CoordsPair2)). + not sets:is_disjoint(Coords1Set, coordpairs_sets(CoordsPair2)). +coordpairs_sets(List) -> + wings_util:sets_from_list(List). rv_coordpairs_from_shapes(SL) -> CoordPairs = lists:append([rv_coordpairs_from_shapes_1(S) || S <- SL]), - sets:from_list(CoordPairs). + coordpairs_sets(CoordPairs). rv_coordpairs_from_shapes_1(#shape_piece{geometry=Geom1}) -> #geometry{coords=Coords1,coordIndices=Coords1I}=Geom1, reverse_coord_pairs(Coords1,Coords1I). diff --git a/src/wings_util.erl b/src/wings_util.erl index b05d4b7d5..426abe414 100644 --- a/src/wings_util.erl +++ b/src/wings_util.erl @@ -21,6 +21,7 @@ cap/1,upper/1,stringify/1,quote/1, add_vpos/2,update_vpos/2, gb_trees_smallest_key/1,gb_trees_largest_key/1, + sets_new/0,sets_from_list/1, array_keys/1,array_smallest_key/1,array_greatest_key/1, array_is_empty/1,array_entries/1, mapsfind/3, @@ -155,6 +156,16 @@ gb_trees_largest_key(Tree) -> {Key,_Val} = gb_trees:largest(Tree), Key. + +%% Create a set with performance options set. +%% +sets_new() -> + sets:new([{version,2}]). + +sets_from_list(List) -> + sets:from_list(List, [{version,2}]). + + array_keys(Array) -> array:sparse_foldr(fun(I, _, A) -> [I|A] end, [], Array).